[
  {
    "path": ".clang-format",
    "content": "BasedOnStyle: Google\nColumnLimit: 0\nIndentWidth: 4\nSortIncludes: Never\nSpacesBeforeTrailingComments: 1\nNamespaceIndentation: All\nAccessModifierOffset: -4\nSpaceAfterCStyleCast: true\nSpaceAfterTemplateKeyword: false\nSpaceBeforeRangeBasedForLoopColon: false\n"
  },
  {
    "path": ".clang-tidy",
    "content": "# Generated from CLion Inspection settings\n---\nChecks: '-*,\nbugprone-argument-comment,\nbugprone-assert-side-effect,\nbugprone-bad-signal-to-kill-thread,\nbugprone-branch-clone,\nbugprone-copy-constructor-init,\nbugprone-dangling-handle,\nbugprone-dynamic-static-initializers,\nbugprone-fold-init-type,\nbugprone-forward-declaration-namespace,\nbugprone-forwarding-reference-overload,\nbugprone-inaccurate-erase,\nbugprone-incorrect-roundings,\nbugprone-integer-division,\nbugprone-lambda-function-name,\nbugprone-macro-parentheses,\nbugprone-macro-repeated-side-effects,\nbugprone-misplaced-operator-in-strlen-in-alloc,\nbugprone-misplaced-pointer-arithmetic-in-alloc,\nbugprone-misplaced-widening-cast,\nbugprone-move-forwarding-reference,\nbugprone-multiple-statement-macro,\nbugprone-no-escape,\nbugprone-not-null-terminated-result,\nbugprone-parent-virtual-call,\nbugprone-posix-return,\nbugprone-reserved-identifier,\nbugprone-sizeof-container,\nbugprone-sizeof-expression,\nbugprone-spuriously-wake-up-functions,\nbugprone-string-constructor,\nbugprone-string-integer-assignment,\nbugprone-string-literal-with-embedded-nul,\nbugprone-suspicious-enum-usage,\nbugprone-suspicious-include,\nbugprone-suspicious-memory-comparison,\nbugprone-suspicious-memset-usage,\nbugprone-suspicious-missing-comma,\nbugprone-suspicious-semicolon,\nbugprone-suspicious-string-compare,\nbugprone-swapped-arguments,\nbugprone-terminating-continue,\nbugprone-throw-keyword-missing,\nbugprone-too-small-loop-variable,\nbugprone-undefined-memory-manipulation,\nbugprone-undelegated-constructor,\nbugprone-unhandled-self-assignment,\nbugprone-unused-raii,\nbugprone-unused-return-value,\nbugprone-use-after-move,\nbugprone-virtual-near-miss,\ncert-dcl21-cpp,\ncert-dcl58-cpp,\ncert-err34-c,\ncert-err52-cpp,\ncert-err60-cpp,\ncert-flp30-c,\ncert-msc50-cpp,\ncert-msc51-cpp,\ncert-str34-c,\ncppcoreguidelines-interfaces-global-init,\ncppcoreguidelines-narrowing-conversions,\ncppcoreguidelines-pro-type-member-init,\ncppcoreguidelines-pro-type-static-cast-downcast,\ncppcoreguidelines-slicing,\ngoogle-default-arguments,\ngoogle-explicit-constructor,\ngoogle-runtime-operator,\nhicpp-exception-baseclass,\nhicpp-multiway-paths-covered,\nmisc-misplaced-const,\nmisc-new-delete-overloads,\nmisc-no-recursion,\nmisc-non-copyable-objects,\nmisc-throw-by-value-catch-by-reference,\nmisc-unconventional-assign-operator,\nmisc-uniqueptr-reset-release,\nmodernize-avoid-bind,\nmodernize-concat-nested-namespaces,\nmodernize-deprecated-headers,\nmodernize-deprecated-ios-base-aliases,\nmodernize-loop-convert,\nmodernize-make-shared,\nmodernize-make-unique,\nmodernize-pass-by-value,\nmodernize-raw-string-literal,\nmodernize-redundant-void-arg,\nmodernize-replace-auto-ptr,\nmodernize-replace-disallow-copy-and-assign-macro,\nmodernize-replace-random-shuffle,\nmodernize-return-braced-init-list,\nmodernize-shrink-to-fit,\nmodernize-unary-static-assert,\nmodernize-use-auto,\nmodernize-use-bool-literals,\nmodernize-use-emplace,\nmodernize-use-equals-default,\nmodernize-use-equals-delete,\nmodernize-use-nodiscard,\nmodernize-use-noexcept,\nmodernize-use-nullptr,\nmodernize-use-override,\nmodernize-use-transparent-functors,\nmodernize-use-uncaught-exceptions,\nmpi-buffer-deref,\nmpi-type-mismatch,\nopenmp-use-default-none,\nperformance-faster-string-find,\nperformance-for-range-copy,\nperformance-implicit-conversion-in-loop,\nperformance-inefficient-algorithm,\nperformance-inefficient-string-concatenation,\nperformance-inefficient-vector-operation,\nperformance-move-const-arg,\nperformance-move-constructor-init,\nperformance-no-automatic-move,\nperformance-noexcept-move-constructor,\nperformance-trivially-destructible,\nperformance-type-promotion-in-math-fn,\nperformance-unnecessary-copy-initialization,\nperformance-unnecessary-value-param,\nportability-simd-intrinsics,\nreadability-avoid-const-params-in-decls,\nreadability-const-return-type,\nreadability-container-size-empty,\nreadability-convert-member-functions-to-static,\nreadability-delete-null-pointer,\nreadability-deleted-default,\nreadability-inconsistent-declaration-parameter-name,\nreadability-make-member-function-const,\nreadability-misleading-indentation,\nreadability-misplaced-array-index,\nreadability-non-const-parameter,\nreadability-redundant-control-flow,\nreadability-redundant-declaration,\nreadability-redundant-function-ptr-dereference,\nreadability-redundant-smartptr-get,\nreadability-redundant-string-cstr,\nreadability-redundant-string-init,\nreadability-simplify-subscript-expr,\nreadability-static-accessed-through-instance,\nreadability-static-definition-in-anonymous-namespace,\nreadability-string-compare,\nreadability-uniqueptr-delete-release,\nreadability-use-anyofallof'"
  },
  {
    "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/build-nekoray-cmake.yml",
    "content": "name: Nekoray build matrix - cmake\n\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      artifact-pack:\n        description: \"artifact-pack: If want ignore\"\n        required: false\njobs:\n  build-go:\n    strategy:\n      matrix:\n        cross_os: [windows, linux]\n        cross_arch: [amd64]\n        include:\n          - cross_os: public_res\n            cross_arch: public_res\n      fail-fast: false\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checking out sources\n        uses: actions/checkout@v3\n      - name: Go Status\n        run: git ls-files go | xargs cat | sha1sum > go_status\n      - name: Cache Common Download\n        id: cache-common\n        uses: actions/cache@v3\n        with:\n          path: artifacts.tgz\n          key: CommonCache-${{ matrix.cross_os }}-${{ matrix.cross_arch }}-${{ hashFiles('libs/*.sh', 'go_status', '*.txt') }}\n      - name: Install Golang\n        if: steps.cache-common.outputs.cache-hit != 'true'\n        uses: actions/setup-go@v3\n        with:\n          go-version: ^1.22\n      - name: Build golang parts\n        if: steps.cache-common.outputs.cache-hit != 'true'\n        shell: bash\n        run: |\n          [ ${{ matrix.cross_os }} == public_res ] || ./libs/get_source.sh\n          [ ${{ matrix.cross_os }} == public_res ] || GOOS=${{ matrix.cross_os }} GOARCH=${{ matrix.cross_arch }} ./libs/build_go.sh\n          [ ${{ matrix.cross_os }} == public_res ] || exit 0\n          ./libs/build_public_res.sh\n      - name: Tar files\n        if: steps.cache-common.outputs.cache-hit != 'true'\n        run: tar czvf artifacts.tgz ./deployment\n      - name: Uploading Artifact\n        uses: actions/upload-artifact@v3\n        with:\n          name: NekoRay-${{ github.sha }}-Common-${{ matrix.cross_os }}-${{ matrix.cross_arch }}\n          path: artifacts.tgz\n  build-cpp:\n    strategy:\n      matrix:\n        include:\n          - platform: windows-2022\n            arch: x64\n            qt_version: \"6.7\"\n          - platform: ubuntu-20.04\n            arch: x64\n            qt_version: \"5.12\"\n      fail-fast: false\n\n    runs-on: ${{ matrix.platform }}\n    env:\n      ACTIONS_ALLOW_UNSECURE_COMMANDS: true\n    steps:\n      - name: Checking out sources\n        uses: actions/checkout@v3\n        with:\n          submodules: \"recursive\"\n      - name: Install MSVC compiler\n        if: matrix.platform == 'windows-2022'\n        uses: ilammy/msvc-dev-cmd@v1\n        with:\n          # 14.1 is for vs2017, 14.2 is vs2019, following the upstream vcpkg build from Qv2ray-deps repo\n          toolset: 14.2\n          arch: ${{ matrix.arch }}\n      # ========================================================================================================= Qt Install\n      - name: Windows - Download Custom Qt ${{ matrix.qt_version }} SDK\n        shell: bash\n        if: matrix.platform == 'windows-2022'\n        env:\n          DL_QT_VER: ${{ matrix.qt_version }}\n        run: bash ./libs/download_qtsdk_win.sh\n      # ========================================================================================================= 编译与 Qt 无关的依赖\n      - name: Install ninja-build tool\n        uses: seanmiddleditch/gha-setup-ninja@v3\n      - name: Cache Download\n        id: cache-deps\n        uses: actions/cache@v3\n        with:\n          path: libs/deps\n          key: DepsCache-${{ matrix.platform }}-${{ matrix.arch }}-${{ hashFiles('libs/build_deps_*.sh') }}-Qt${{ matrix.qt_version }}\n      - name: Build Dependencies\n        shell: bash\n        if: steps.cache-deps.outputs.cache-hit != 'true' && matrix.platform != 'ubuntu-20.04'\n        run: ./libs/build_deps_all.sh\n      - name: Build Dependencies (Docker)\n        shell: bash\n        if: steps.cache-deps.outputs.cache-hit != 'true' && matrix.platform == 'ubuntu-20.04'\n        run: |\n          docker run --rm \\\n            -v $PWD:/nekoray \\\n            -w /nekoray \\\n            ghcr.io/matsuridayo/debian10-qt5:20230131 \\\n            bash -c \"./libs/build_deps_all.sh\"\n      # ========================================================================================================= Generate MakeFile and Build\n      - name: Windows - Generate MakeFile and Build\n        shell: bash\n        if: matrix.platform == 'windows-2022'\n        env:\n          DL_QT_VER: ${{ matrix.qt_version }}\n          CC: cl.exe\n          CXX: cl.exe\n        run: |\n          source libs/env_qtsdk.sh $PWD/qtsdk/Qt\n          mkdir build\n          cd build\n          cmake -GNinja -DQT_VERSION_MAJOR=6 -DCMAKE_BUILD_TYPE=Release ..\n          ninja -j2\n          cd ..\n          ./libs/deploy_windows64.sh\n      - name: Linux - Generate MakeFile and Build\n        shell: bash\n        if: matrix.platform == 'ubuntu-20.04'\n        run: |\n          docker run --rm \\\n            -v $PWD:/nekoray \\\n            -w /nekoray \\\n            ghcr.io/matsuridayo/debian10-qt5:20230131 \\\n            bash -c \"mkdir build && pushd build && cmake -GNinja -DCMAKE_BUILD_TYPE=Release .. && ninja && popd &&./libs/deploy_linux64.sh\"\n      # ========================================================================================================= Deployments\n      - name: Tar files\n        shell: bash\n        run: tar czvf artifacts.tgz ./deployment\n      - name: Uploading Artifact\n        uses: actions/upload-artifact@v3\n        with:\n          name: NekoRay-${{ github.sha }}-${{ matrix.platform }}-${{ matrix.arch }}-Qt${{ matrix.qt_version }}\n          path: artifacts.tgz\n  publish:\n    name: Pack & Publish Release\n    if: github.event.inputs.artifact-pack != 'y'\n    runs-on: ubuntu-latest\n    needs:\n      - build-cpp\n      - build-go\n    steps:\n      - name: Checking out sources\n        uses: actions/checkout@v3\n      - name: Download Artifacts\n        uses: actions/download-artifact@v3\n        with:\n          path: download-artifact\n      - name: Pack\n        run: |\n          curl -Lo - https://github.com/tcnksm/ghr/releases/download/v0.13.0/ghr_v0.13.0_linux_amd64.tar.gz | tar xzv\n          mv ghr*linux_amd64/ghr .\n          ####\n          source libs/env_deploy.sh\n          find . -name artifacts.tgz | xargs -n1 tar xvzf\n          cd deployment\n          cp -r public_res/* linux64\n          cp -r public_res/* windows64\n          rm -rf public_res *.pdb\n          ####\n          mv linux64 nekoray\n          zip -r $version_standalone-linux64.zip nekoray\n          rm -rf nekoray\n          ####\n          mv windows64 nekoray\n          zip -r $version_standalone-windows64.zip nekoray\n          rm -rf nekoray\n      - name: Pack Debian\n        run: |\n          source libs/env_deploy.sh\n          find . -name artifacts.tgz | xargs -n1 tar xvzf\n          cd deployment\n          cp -r public_res/* linux64\n          ####\n          bash ../libs/package_debian.sh ${{ github.event.inputs.tag }}\n          mv nekoray.deb $version_standalone-debian-x64.deb\n          sudo rm -rf nekoray\n      - name: Pack AppImage\n        run: |\n          source libs/env_deploy.sh\n          find . -name artifacts.tgz | xargs -n1 tar xvzf\n          cd deployment\n          cp -r public_res/* linux64\n          ####\n          bash ../libs/package_appimage.sh\n          mv nekobox-x86_64.AppImage $version_standalone-linux-x64.AppImage\n      - name: Clean Up\n        run: |\n          cd deployment\n          rm -rf linux64\n          rm -rf windows64\n          rm -rf public_res\n          rm -rf *.pdb\n      - name: Uploading Artifact\n        uses: actions/upload-artifact@v3\n        with:\n          name: Deployment-${{ github.sha }}\n          path: deployment\n      - name: Release\n        if: github.event.inputs.publish != 'y'\n        run: |\n          ./ghr -delete -t \"${{ github.token }}\" -n \"${{ github.event.inputs.tag }}\" \"${{ github.event.inputs.tag }}\" deployment\n"
  },
  {
    "path": ".github/workflows/update-pkgbuild.yml",
    "content": "name: AUR CI\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - '**.md'\n      - 'LICENSE'\n      - '!.github/workflows/**'\n\njobs:\n  update:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: chitang233/aur-pkgbuild-builder@main\n        with:\n          deploy_key: ${{ secrets.DEPLOY_KEY }}\n          package_name: 'nekoray-git'"
  },
  {
    "path": ".gitignore",
    "content": "# This file is used to ignore files which are generated\n# ----------------------------------------------------------------------------\n\n*~\n*.autosave\n*.a\n*.core\n*.moc\n*.o\n*.obj\n*.orig\n*.rej\n*.so\n*.so.*\n*_pch.h.cpp\n*_resource.rc\n.#*\n*.*#\ncore\n!core/\ntags\n.DS_Store\n.directory\n*.debug\n/Makefile*\n*.prl\n*.app\nmoc_*.cpp\nui_*.h\nqrc_*.cpp\nThumbs.db\n*.res\n/.qmake.cache\n/.qmake.stash\n\n# qtcreator generated files\n*.pro.user*\n\n# xemacs temporary files\n*.flc\n\n# Vim temporary files\n.*.swp\n\n# Visual Studio generated files\n*.ib_pdb_index\n*.idb\n*.ilk\n*.pdb\n*.sln\n*.suo\n*.vcproj\n*vcproj.*.*.user\n*.ncb\n*.sdf\n*.opensdf\n*.vcxproj\n*vcxproj.*\n\n# MinGW generated files\n*.Debug\n*.Release\n\n# Python byte code\n*.pyc\n\n# Binaries\n# --------\n*.dll\n*.exe\n\n# Custom\n/nekoray\n/build\nCMakeLists.txt.user*\n/cmake-build-*\n/build-*\n.vscode\n.idea\n\n# Deploy\n/deployment\n/neko*.sh\n/qtsdk\n\n.vs\nout"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"3rdparty/QHotkey\"]\n\tpath = 3rdparty/QHotkey\n\turl = https://github.com/Skycoder42/QHotkey.git\n"
  },
  {
    "path": "3rdparty/QThreadCreateThread.hpp",
    "content": "#pragma once\n\n#include <future>\n#include <QThread>\n\n// FOR OLD QT\n\nclass QThreadCreateThread : public QThread {\npublic:\n    explicit QThreadCreateThread(std::future<void> &&future)\n            : m_future(std::move(future)) {\n        // deleteLater\n        connect(this, &QThread::finished, this, &QThread::deleteLater);\n    }\n\nprivate:\n    void run() override {\n        m_future.get();\n    }\n\n    std::future<void> m_future;\n};\n\ninline QThread *createThreadImpl(std::future<void> &&future) {\n    return new QThreadCreateThread(std::move(future));\n}\n\ntemplate<typename Function, typename... Args>\nQThread *createQThread(Function &&f, Args &&... args) {\n    using DecayedFunction = typename std::decay<Function>::type;\n    auto threadFunction =\n            [f = static_cast<DecayedFunction>(std::forward<Function>(f))](auto &&... largs) mutable -> void {\n                (void) std::invoke(std::move(f), std::forward<decltype(largs)>(largs)...);\n            };\n\n    return createThreadImpl(std::async(std::launch::deferred,\n                                       std::move(threadFunction),\n                                       std::forward<Args>(args)...));\n}\n"
  },
  {
    "path": "3rdparty/QtExtKeySequenceEdit.cpp",
    "content": "#include \"QtExtKeySequenceEdit.h\"\n\nQtExtKeySequenceEdit::QtExtKeySequenceEdit(QWidget *parent)\n        : QKeySequenceEdit(parent) {\n}\n\nQtExtKeySequenceEdit::~QtExtKeySequenceEdit() {\n}\n\nvoid QtExtKeySequenceEdit::keyPressEvent(QKeyEvent *pEvent) {\n    QKeySequenceEdit::keyPressEvent(pEvent);\n\n    QKeySequence keySeq = keySequence();\n    if (keySeq.count() <= 0) {\n        return;\n    }\n    int key = keySeq[0];\n    if (key == Qt::Key_Backspace || key == Qt::Key_Delete) {\n        key = 0;\n    }\n    setKeySequence(key);\n}\n"
  },
  {
    "path": "3rdparty/QtExtKeySequenceEdit.h",
    "content": "#include <QKeySequenceEdit>\n\nclass QtExtKeySequenceEdit : public QKeySequenceEdit {\npublic:\n    QtExtKeySequenceEdit(QWidget *parent);\n\n    ~QtExtKeySequenceEdit();\n\nprotected:\n    virtual void keyPressEvent(QKeyEvent *pEvent);\n};\n"
  },
  {
    "path": "3rdparty/RunGuard.hpp",
    "content": "#ifndef RUNGUARD_H\n#define RUNGUARD_H\n\n#include <QObject>\n#include <QSharedMemory>\n#include <QSystemSemaphore>\n#include <QCryptographicHash>\n\nclass RunGuard {\npublic:\n    RunGuard(const QString &key);\n\n    ~RunGuard();\n\n    bool isAnotherRunning(quint64 *data_out);\n\n    bool tryToRun(quint64 *data_in);\n\n    void release();\n\nprivate:\n    const QString key;\n    const QString memLockKey;\n    const QString sharedmemKey;\n\n    QSharedMemory sharedMem;\n    QSystemSemaphore memLock;\n\n    Q_DISABLE_COPY(RunGuard)\n};\n\nnamespace {\n\n    QString generateKeyHash(const QString &key, const QString &salt) {\n        QByteArray data;\n\n        data.append(key.toUtf8());\n        data.append(salt.toUtf8());\n        data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex();\n\n        return data;\n    }\n\n} // namespace\n\nRunGuard::RunGuard(const QString &key)\n    : key(key), memLockKey(generateKeyHash(key, \"_memLockKey\")), sharedmemKey(generateKeyHash(key, \"_sharedmemKey\")), sharedMem(sharedmemKey), memLock(memLockKey, 1) {\n    memLock.acquire();\n    {\n        QSharedMemory fix(sharedmemKey); // Fix for *nix: http://habrahabr.ru/post/173281/\n        fix.attach();\n    }\n    memLock.release();\n}\n\nRunGuard::~RunGuard() {\n    release();\n}\n\nbool RunGuard::isAnotherRunning(quint64 *data_out) {\n    if (sharedMem.isAttached())\n        return false;\n\n    memLock.acquire();\n    const bool isRunning = sharedMem.attach();\n    if (isRunning) {\n        if (data_out != nullptr) {\n            memcpy(data_out, sharedMem.data(), sizeof(quint64));\n        }\n        sharedMem.detach();\n    }\n    memLock.release();\n\n    return isRunning;\n}\n\nbool RunGuard::tryToRun(quint64 *data_in) {\n    memLock.acquire();\n    const bool result = sharedMem.create(sizeof(quint64));\n    if (result) memcpy(sharedMem.data(), data_in, sizeof(quint64));\n    memLock.release();\n\n    if (!result) {\n        release();\n        return false;\n    }\n\n    return true;\n}\n\nvoid RunGuard::release() {\n    memLock.acquire();\n    if (sharedMem.isAttached())\n        sharedMem.detach();\n    memLock.release();\n}\n\n#endif // RUNGUARD_H\n"
  },
  {
    "path": "3rdparty/VT100Parser.hpp",
    "content": "#pragma once\n\n#include <QString>\n\ninline QString cleanVT100String(const QString &in) {\n    QString out;\n    bool in_033 = false;\n    for (auto &&chr: in) {\n        if (chr == '\\033') {\n            in_033 = true;\n            continue;\n        }\n        if (in_033) {\n            if (chr == 'm') {\n                in_033 = false;\n            }\n            continue;\n        }\n        out += chr;\n    }\n    return out;\n}\n"
  },
  {
    "path": "3rdparty/WinCommander.cpp",
    "content": "/****************************************************************************\n**\n** Copyright (C) 2014 UpdateNode UG (haftungsbeschränkt)\n** Contact: code@updatenode.com\n**\n** This file is part of the UpdateNode Client.\n**\n** Commercial License Usage\n** Licensees holding valid commercial UpdateNode license may use this file\n** under the terms of the the Apache License, Version 2.0\n** Full license description file: LICENSE.COM\n**\n** GNU General Public License Usage\n** Alternatively, this file may be used under the terms of the GNU\n** General Public License version 3.0 as published by the Free Software\n** Foundation. Please review the following information to ensure the\n** GNU General Public License version 3.0 requirements will be met:\n** http://www.gnu.org/copyleft/gpl.html.\n** Full license description file: LICENSE.GPL\n**\n****************************************************************************/\n\n#include \"WinCommander.hpp\"\n\n#include <QSysInfo>\n#include <QDir>\n\n#ifdef Q_OS_WIN\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#include <windows.h>\n#include <shellapi.h>\n#include <sddl.h>\n#define MAX_KEY_LENGTH 255\n#define MAX_VALUE_NAME 16383\n#endif\n\n\n/*!\nExecutes a command elevated specified by \\apath , using paramters \\aparameters.\n\\n\nParameter /aaWait decides if the function should return immediatelly after it's\\n\nexecution or wait for the exit of the launched process\n\\n\nReturns the return value of the executed command\n*/\nuint WinCommander::runProcessElevated(const QString &path,\n                                      const QStringList &parameters,\n                                      const QString &workingDir,\n                                      int nShow, bool aWait) {\n    uint result = 0;\n\n#ifdef Q_OS_WIN\n    QString params;\n    HWND hwnd = NULL;\n    LPCTSTR pszPath = (LPCTSTR)path.utf16();\n    foreach(QString item, parameters)\n        params += \"\\\"\" + item + \"\\\" \";\n\n    LPCTSTR pszParameters = (LPCTSTR)params.utf16();\n    QString dir;\n    if (workingDir.count() == 0)\n        dir = QDir::toNativeSeparators(QDir::currentPath());\n    else\n        dir = QDir::toNativeSeparators(workingDir);\n    LPCTSTR pszDirectory = (LPCTSTR)dir.utf16();\n\n    SHELLEXECUTEINFO shex;\n    DWORD dwCode  =   0;\n\n    ZeroMemory(&shex, sizeof(shex));\n\n    shex.cbSize       = sizeof(shex);\n    shex.fMask        = SEE_MASK_NOCLOSEPROCESS;\n    shex.hwnd         = hwnd;\n    shex.lpVerb       = TEXT(\"runas\");\n    shex.lpFile       = pszPath;\n    shex.lpParameters = pszParameters;\n    shex.lpDirectory  = pszDirectory;\n    // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow\n    shex.nShow        = nShow;\n\n    ShellExecuteEx(&shex);\n    if (shex.hProcess)\n    {\n        if(aWait)\n        {\n            WaitForSingleObject(shex.hProcess, INFINITE );\n            GetExitCodeProcess(shex.hProcess, &dwCode);\n        }\n        CloseHandle (shex.hProcess) ;\n    }\n    else\n        return -1;\n\n    result = (uint)dwCode;\n#else\n    Q_UNUSED(path);\n    Q_UNUSED(parameters);\n    Q_UNUSED(workingDir);\n    Q_UNUSED(aWait);\n#endif\n    return result;\n}\n"
  },
  {
    "path": "3rdparty/WinCommander.hpp",
    "content": "/****************************************************************************\n**\n** Copyright (C) 2014 UpdateNode UG (haftungsbeschränkt)\n** Contact: code@updatenode.com\n**\n** This file is part of the UpdateNode Client.\n**\n** Commercial License Usage\n** Licensees holding valid commercial UpdateNode license may use this file\n** under the terms of the the Apache License, Version 2.0\n** Full license description file: LICENSE.COM\n**\n** GNU General Public License Usage\n** Alternatively, this file may be used under the terms of the GNU\n** General Public License version 3.0 as published by the Free Software\n** Foundation. Please review the following information to ensure the\n** GNU General Public License version 3.0 requirements will be met:\n** http://www.gnu.org/copyleft/gpl.html.\n** Full license description file: LICENSE.GPL\n**\n****************************************************************************/\n\n#ifndef WINCOMMANDER_H\n#define WINCOMMANDER_H\n\n#include <QString>\n#include <QStringList>\n\nclass WinCommander {\npublic:\n    static const int SW_HIDE = 0;\n    static const int SW_NORMAL = 1;\n    static const int SW_SHOWMINIMIZED = 2;\n\n    static uint runProcessElevated(const QString &path,\n                                   const QStringList &parameters = QStringList(),\n                                   const QString &workingDir = QString(),\n                                   int nShow = SW_SHOWMINIMIZED, bool aWait = true);\n};\n\n#endif // WINCOMMANDER_H"
  },
  {
    "path": "3rdparty/ZxingQtReader.hpp",
    "content": "/*\n * Copyright 2020 Axel Waggershauser\n */\n// SPDX-License-Identifier: Apache-2.0\n\n#pragma once\n\n#include \"ZXing/ReadBarcode.h\"\n\n#include <QImage>\n#include <QDebug>\n#include <QMetaType>\n#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)\n#include \"qscopeguard.h\"\n#else\n#include <QScopeGuard>\n#endif\n\n#ifdef QT_MULTIMEDIA_LIB\n#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)\n#include <QAbstractVideoFilter>\n#else\n#include <QVideoFrame>\n#include <QVideoSink>\n#endif\n#include <QElapsedTimer>\n#endif\n\n// This is some sample code to start a discussion about how a minimal and header-only Qt wrapper/helper could look like.\n\nnamespace ZXingQt {\n\nQ_NAMESPACE\n\n//TODO: find a better way to export these enums to QML than to duplicate their definition\n// #ifdef Q_MOC_RUN produces meta information in the moc output but it does end up working in qml\n#ifdef QT_QML_LIB\nenum class BarcodeFormat\n{\n\tNone            = 0,         ///< Used as a return value if no valid barcode has been detected\n\tAztec           = (1 << 0),  ///< Aztec\n\tCodabar         = (1 << 1),  ///< Codabar\n\tCode39          = (1 << 2),  ///< Code39\n\tCode93          = (1 << 3),  ///< Code93\n\tCode128         = (1 << 4),  ///< Code128\n\tDataBar         = (1 << 5),  ///< GS1 DataBar, formerly known as RSS 14\n\tDataBarExpanded = (1 << 6),  ///< GS1 DataBar Expanded, formerly known as RSS EXPANDED\n\tDataMatrix      = (1 << 7),  ///< DataMatrix\n\tEAN8            = (1 << 8),  ///< EAN-8\n\tEAN13           = (1 << 9),  ///< EAN-13\n\tITF             = (1 << 10), ///< ITF (Interleaved Two of Five)\n\tMaxiCode        = (1 << 11), ///< MaxiCode\n\tPDF417          = (1 << 12), ///< PDF417 or\n\tQRCode          = (1 << 13), ///< QR Code\n\tUPCA            = (1 << 14), ///< UPC-A\n\tUPCE            = (1 << 15), ///< UPC-E\n\tMicroQRCode     = (1 << 16), ///< Micro QR Code\n\n\tLinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | UPCA | UPCE,\n\tMatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode,\n};\n\nenum class ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI };\n\n#else\nusing ZXing::BarcodeFormat;\nusing ZXing::ContentType;\n#endif\n\nusing ZXing::DecodeHints;\nusing ZXing::Binarizer;\nusing ZXing::BarcodeFormats;\n\nQ_ENUM_NS(BarcodeFormat)\nQ_ENUM_NS(ContentType)\n\ntemplate<typename T, typename = decltype(ZXing::ToString(T()))>\nQDebug operator<<(QDebug dbg, const T& v)\n{\n\treturn dbg.noquote() << QString::fromStdString(ToString(v));\n}\n\nclass Position : public ZXing::Quadrilateral<QPoint>\n{\n\tQ_GADGET\n\n\tQ_PROPERTY(QPoint topLeft READ topLeft)\n\tQ_PROPERTY(QPoint topRight READ topRight)\n\tQ_PROPERTY(QPoint bottomRight READ bottomRight)\n\tQ_PROPERTY(QPoint bottomLeft READ bottomLeft)\n\n\tusing Base = ZXing::Quadrilateral<QPoint>;\n\npublic:\n\tusing Base::Base;\n};\n\nclass Result : private ZXing::Result\n{\n\tQ_GADGET\n\n\tQ_PROPERTY(BarcodeFormat format READ format)\n\tQ_PROPERTY(QString formatName READ formatName)\n\tQ_PROPERTY(QString text READ text)\n\tQ_PROPERTY(QByteArray bytes READ bytes)\n\tQ_PROPERTY(bool isValid READ isValid)\n\tQ_PROPERTY(ContentType contentType READ contentType)\n\tQ_PROPERTY(Position position READ position)\n\n\tQString _text;\n\tQByteArray _bytes;\n\tPosition _position;\n\npublic:\n\tResult() = default; // required for qmetatype machinery\n\n\texplicit Result(ZXing::Result&& r) : ZXing::Result(std::move(r)) {\n\t\t_text = QString::fromStdString(ZXing::Result::text());\n\t\t_bytes = QByteArray(reinterpret_cast<const char*>(ZXing::Result::bytes().data()), Size(ZXing::Result::bytes()));\n\t\tauto& pos = ZXing::Result::position();\n\t\tauto qp = [&pos](int i) { return QPoint(pos[i].x, pos[i].y); };\n\t\t_position = {qp(0), qp(1), qp(2), qp(3)};\n\t}\n\n\tusing ZXing::Result::isValid;\n\n\tBarcodeFormat format() const { return static_cast<BarcodeFormat>(ZXing::Result::format()); }\n\tContentType contentType() const { return static_cast<ContentType>(ZXing::Result::contentType()); }\n\tQString formatName() const { return QString::fromStdString(ZXing::ToString(ZXing::Result::format())); }\n\tconst QString& text() const { return _text; }\n\tconst QByteArray& bytes() const { return _bytes; }\n\tconst Position& position() const { return _position; }\n\n\t// For debugging/development\n\tint runTime = 0;\n\tQ_PROPERTY(int runTime MEMBER runTime)\n};\n\ninline QList<Result> QListResults(ZXing::Results&& zxres)\n{\n\tQList<Result> res;\n\tfor (auto&& r : zxres)\n\t\tres.push_back(Result(std::move(r)));\n\treturn res;\n}\n\ninline QList<Result> ReadBarcodes(const QImage& img, const DecodeHints& hints = {})\n{\n\tusing namespace ZXing;\n\n\tauto ImgFmtFromQImg = [](const QImage& img) {\n\t\tswitch (img.format()) {\n\t\tcase QImage::Format_ARGB32:\n\t\tcase QImage::Format_RGB32:\n#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN\n\t\t\treturn ImageFormat::BGRX;\n#else\n\t\t\treturn ImageFormat::XRGB;\n#endif\n\t\tcase QImage::Format_RGB888: return ImageFormat::RGB;\n\t\tcase QImage::Format_RGBX8888:\n\t\tcase QImage::Format_RGBA8888: return ImageFormat::RGBX;\n\t\tcase QImage::Format_Grayscale8: return ImageFormat::Lum;\n\t\tdefault: return ImageFormat::None;\n\t\t}\n\t};\n\n\tauto exec = [&](const QImage& img) {\n\t\treturn QListResults(ZXing::ReadBarcodes(\n\t\t\t{img.bits(), img.width(), img.height(), ImgFmtFromQImg(img), static_cast<int>(img.bytesPerLine())}, hints));\n\t};\n\n\treturn ImgFmtFromQImg(img) == ImageFormat::None ? exec(img.convertToFormat(QImage::Format_Grayscale8)) : exec(img);\n}\n\ninline Result ReadBarcode(const QImage& img, const DecodeHints& hints = {})\n{\n\tauto res = ReadBarcodes(img, DecodeHints(hints).setMaxNumberOfSymbols(1));\n\treturn !res.isEmpty() ? res.takeFirst() : Result();\n}\n\n#ifdef QT_MULTIMEDIA_LIB\ninline QList<Result> ReadBarcodes(const QVideoFrame& frame, const DecodeHints& hints = {})\n{\n\tusing namespace ZXing;\n\n\tImageFormat fmt = ImageFormat::None;\n\tint pixStride = 0;\n\tint pixOffset = 0;\n\n#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)\n#define FORMAT(F5, F6) QVideoFrame::Format_##F5\n#define FIRST_PLANE\n#else\n#define FORMAT(F5, F6) QVideoFrameFormat::Format_##F6\n#define FIRST_PLANE 0\n#endif\n\n\tswitch (frame.pixelFormat()) {\n\tcase FORMAT(ARGB32, ARGB8888):\n\tcase FORMAT(ARGB32_Premultiplied, ARGB8888_Premultiplied):\n\tcase FORMAT(RGB32, RGBX8888):\n#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN\n\t\tfmt = ImageFormat::BGRX;\n#else\n\t\tfmt = ImageFormat::XRGB;\n#endif\n\t\tbreak;\n\n\tcase FORMAT(BGRA32, BGRA8888):\n\tcase FORMAT(BGRA32_Premultiplied, BGRA8888_Premultiplied):\n\tcase FORMAT(BGR32, BGRX8888):\n#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN\n\t\tfmt = ImageFormat::RGBX;\n#else\n\t\tfmt = ImageFormat::XBGR;\n#endif\n\t\tbreak;\n\n#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)\n\tcase QVideoFrame::Format_RGB24: fmt = ImageFormat::RGB; break;\n\tcase QVideoFrame::Format_BGR24: fmt = ImageFormat::BGR; break;\n\tcase QVideoFrame::Format_YUV444: fmt = ImageFormat::Lum, pixStride = 3; break;\n#else\n\tcase QVideoFrameFormat::Format_P010:\n\tcase QVideoFrameFormat::Format_P016: fmt = ImageFormat::Lum, pixStride = 1; break;\n#endif\n\n\tcase FORMAT(AYUV444, AYUV):\n\tcase FORMAT(AYUV444_Premultiplied, AYUV_Premultiplied):\n#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN\n\t\tfmt = ImageFormat::Lum, pixStride = 4, pixOffset = 3;\n#else\n\t\tfmt = ImageFormat::Lum, pixStride = 4, pixOffset = 2;\n#endif\n\t\tbreak;\n\n\tcase FORMAT(YUV420P, YUV420P):\n\tcase FORMAT(NV12, NV12):\n\tcase FORMAT(NV21, NV21):\n\tcase FORMAT(IMC1, IMC1):\n\tcase FORMAT(IMC2, IMC2):\n\tcase FORMAT(IMC3, IMC3):\n\tcase FORMAT(IMC4, IMC4):\n\tcase FORMAT(YV12, YV12): fmt = ImageFormat::Lum; break;\n\tcase FORMAT(UYVY, UYVY): fmt = ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;\n\tcase FORMAT(YUYV, YUYV): fmt = ImageFormat::Lum, pixStride = 2; break;\n\n\tcase FORMAT(Y8, Y8): fmt = ImageFormat::Lum; break;\n\tcase FORMAT(Y16, Y16): fmt = ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;\n\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))\n\tcase FORMAT(ABGR32, ABGR8888):\n#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN\n\t\tfmt = ImageFormat::RGBX;\n#else\n\t\tfmt = ImageFormat::XBGR;\n#endif\n\t\tbreak;\n#endif\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))\n\tcase FORMAT(YUV422P, YUV422P): fmt = ImageFormat::Lum; break;\n#endif\n\tdefault: break;\n\t}\n\n\tif (fmt != ImageFormat::None) {\n\t\tauto img = frame; // shallow copy just get access to non-const map() function\n#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)\n\t\tif (!img.isValid() || !img.map(QAbstractVideoBuffer::ReadOnly)){\n#else\n\t\tif (!img.isValid() || !img.map(QVideoFrame::ReadOnly)){\n#endif\n\t\t\tqWarning() << \"invalid QVideoFrame: could not map memory\";\n\t\t\treturn {};\n\t\t}\n\t\tQScopeGuard unmap([&] { img.unmap(); });\n\n\t\treturn QListResults(ZXing::ReadBarcodes(\n\t\t\t{img.bits(FIRST_PLANE) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FIRST_PLANE), pixStride}, hints));\n\t}\n\telse {\n#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)\n\t\tif (QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()) != QImage::Format_Invalid) {\n\t\t\tqWarning() << \"unsupported QVideoFrame::pixelFormat\";\n\t\t\treturn {};\n\t\t}\n\t\tauto qimg = frame.image();\n#else\n\t\tauto qimg = frame.toImage();\n#endif\n\t\tif (qimg.format() != QImage::Format_Invalid)\n\t\t\treturn ReadBarcodes(qimg, hints);\n\t\tqWarning() << \"failed to convert QVideoFrame to QImage\";\n\t\treturn {};\n\t}\n}\n\ninline Result ReadBarcode(const QVideoFrame& frame, const DecodeHints& hints = {})\n{\n\tauto res = ReadBarcodes(frame, DecodeHints(hints).setMaxNumberOfSymbols(1));\n\treturn !res.isEmpty() ? res.takeFirst() : Result();\n}\n\n#define ZQ_PROPERTY(Type, name, setter) \\\npublic: \\\n\tQ_PROPERTY(Type name READ name WRITE setter NOTIFY name##Changed) \\\n\tType name() const noexcept { return DecodeHints::name(); } \\\n\tQ_SLOT void setter(const Type& newVal) \\\n\t{ \\\n\t\tif (name() != newVal) { \\\n\t\t\tDecodeHints::setter(newVal); \\\n\t\t\temit name##Changed(); \\\n\t\t} \\\n\t} \\\n\tQ_SIGNAL void name##Changed();\n\n\n#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)\nclass BarcodeReader : public QAbstractVideoFilter, private DecodeHints\n#else\nclass BarcodeReader : public QObject, private DecodeHints\n#endif\n{\n\tQ_OBJECT\n\npublic:\n#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)\n\tBarcodeReader(QObject* parent = nullptr) : QAbstractVideoFilter(parent) {}\n#else\n\tBarcodeReader(QObject* parent = nullptr) : QObject(parent) {}\n#endif\n\n\t// TODO: find out how to properly expose QFlags to QML\n\t// simply using ZQ_PROPERTY(BarcodeFormats, formats, setFormats)\n\t// results in the runtime error \"can't assign int to formats\"\n\tQ_PROPERTY(int formats READ formats WRITE setFormats NOTIFY formatsChanged)\n\tint formats() const noexcept\n\t{\n\t\tauto fmts = DecodeHints::formats();\n\t\treturn *reinterpret_cast<int*>(&fmts);\n\t}\n\tQ_SLOT void setFormats(int newVal)\n\t{\n\t\tif (formats() != newVal) {\n\t\t\tDecodeHints::setFormats(static_cast<ZXing::BarcodeFormat>(newVal));\n\t\t\temit formatsChanged();\n\t\t\tqDebug() << DecodeHints::formats();\n\t\t}\n\t}\n\tQ_SIGNAL void formatsChanged();\n\n\tZQ_PROPERTY(bool, tryRotate, setTryRotate)\n\tZQ_PROPERTY(bool, tryHarder, setTryHarder)\n\tZQ_PROPERTY(bool, tryDownscale, setTryDownscale)\n\npublic slots:\n\tZXingQt::Result process(const QVideoFrame& image)\n\t{\n\t\tQElapsedTimer t;\n\t\tt.start();\n\n\t\tauto res = ReadBarcode(image, *this);\n\n\t\tres.runTime = t.elapsed();\n\n\t\temit newResult(res);\n\t\tif (res.isValid())\n\t\t\temit foundBarcode(res);\n\t\treturn res;\n\t}\n\nsignals:\n\tvoid newResult(ZXingQt::Result result);\n\tvoid foundBarcode(ZXingQt::Result result);\n\n#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)\npublic:\n\tQVideoFilterRunnable *createFilterRunnable() override;\n#else\nprivate:\n\tQVideoSink *_sink = nullptr;\n\npublic:\n\tvoid setVideoSink(QVideoSink* sink) {\n\t\tif (_sink == sink)\n\t\t\treturn;\n\n\t\tif (_sink)\n\t\t\tdisconnect(_sink, nullptr, this, nullptr);\n\n\t\t_sink = sink;\n\t\tconnect(_sink, &QVideoSink::videoFrameChanged, this, &BarcodeReader::process);\n\t}\n\tQ_PROPERTY(QVideoSink* videoSink WRITE setVideoSink)\n#endif\n\n};\n\n#undef ZX_PROPERTY\n\n#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)\nclass VideoFilterRunnable : public QVideoFilterRunnable\n{\n\tBarcodeReader* _filter = nullptr;\n\npublic:\n\texplicit VideoFilterRunnable(BarcodeReader* filter) : _filter(filter) {}\n\n\tQVideoFrame run(QVideoFrame* input, const QVideoSurfaceFormat& /*surfaceFormat*/, RunFlags /*flags*/) override\n\t{\n\t\t_filter->process(*input);\n\t\treturn *input;\n\t}\n};\n\ninline QVideoFilterRunnable* BarcodeReader::createFilterRunnable()\n{\n\treturn new VideoFilterRunnable(this);\n}\n#endif\n\n#endif // QT_MULTIMEDIA_LIB\n\n} // namespace ZXingQt\n\n\nQ_DECLARE_METATYPE(ZXingQt::Position)\nQ_DECLARE_METATYPE(ZXingQt::Result)\n\n#ifdef QT_QML_LIB\n\n#include <QQmlEngine>\n\nnamespace ZXingQt {\n\ninline void registerQmlAndMetaTypes()\n{\n\tqRegisterMetaType<ZXingQt::BarcodeFormat>(\"BarcodeFormat\");\n\tqRegisterMetaType<ZXingQt::ContentType>(\"ContentType\");\n\n\t// supposedly the Q_DECLARE_METATYPE should be used with the overload without a custom name\n\t// but then the qml side complains about \"unregistered type\"\n\tqRegisterMetaType<ZXingQt::Position>(\"Position\");\n\tqRegisterMetaType<ZXingQt::Result>(\"Result\");\n\n\tqmlRegisterUncreatableMetaObject(\n\t\tZXingQt::staticMetaObject, \"ZXing\", 1, 0, \"ZXing\", \"Access to enums & flags only\");\n\tqmlRegisterType<ZXingQt::BarcodeReader>(\"ZXing\", 1, 0, \"BarcodeReader\");\n}\n\n} // namespace ZXingQt\n\n#endif // QT_QML_LIB"
  },
  {
    "path": "3rdparty/base64.cpp",
    "content": "#include \"base64.h\"\n\n#ifndef qsizetype\n#define qsizetype size_t\n#endif\n\nnamespace Qt515Base64 {\n    namespace {\n        struct fromBase64_helper_result {\n            qsizetype decodedLength;\n            Base64DecodingStatus status;\n        };\n\n        fromBase64_helper_result fromBase64_helper(const char *input, qsizetype inputSize,\n                                                   char *output /* may alias input */,\n                                                   Base64Options options) {\n            fromBase64_helper_result result{0, Base64DecodingStatus::Ok};\n\n            unsigned int buf = 0;\n            int nbits = 0;\n\n            qsizetype offset = 0;\n            for (qsizetype i = 0; i < inputSize; ++i) {\n                int ch = input[i];\n                int d;\n\n                if (ch >= 'A' && ch <= 'Z') {\n                    d = ch - 'A';\n                } else if (ch >= 'a' && ch <= 'z') {\n                    d = ch - 'a' + 26;\n                } else if (ch >= '0' && ch <= '9') {\n                    d = ch - '0' + 52;\n                } else if (ch == '+' && (options & Base64UrlEncoding) == 0) {\n                    d = 62;\n                } else if (ch == '-' && (options & Base64UrlEncoding) != 0) {\n                    d = 62;\n                } else if (ch == '/' && (options & Base64UrlEncoding) == 0) {\n                    d = 63;\n                } else if (ch == '_' && (options & Base64UrlEncoding) != 0) {\n                    d = 63;\n                } else {\n                    if (options & AbortOnBase64DecodingErrors) {\n                        if (ch == '=') {\n                            // can have 1 or 2 '=' signs, in both cases padding base64Size to\n                            // a multiple of 4. Any other case is illegal.\n                            if ((inputSize % 4) != 0) {\n                                result.status = Base64DecodingStatus::IllegalInputLength;\n                                return result;\n                            } else if ((i == inputSize - 1) ||\n                                       (i == inputSize - 2 && input[++i] == '=')) {\n                                d = -1; // ... and exit the loop, normally\n                            } else {\n                                result.status = Base64DecodingStatus::IllegalPadding;\n                                return result;\n                            }\n                        } else {\n                            result.status = Base64DecodingStatus::IllegalCharacter;\n                            return result;\n                        }\n                    } else {\n                        d = -1;\n                    }\n                }\n\n                if (d != -1) {\n                    buf = (buf << 6) | d;\n                    nbits += 6;\n                    if (nbits >= 8) {\n                        nbits -= 8;\n                        Q_ASSERT(offset < i);\n                        output[offset++] = buf >> nbits;\n                        buf &= (1 << nbits) - 1;\n                    }\n                }\n            }\n\n            result.decodedLength = offset;\n            return result;\n        }\n    } // namespace\n\n    FromBase64Result QByteArray_fromBase64Encoding(const QByteArray &base64, Base64Options options) {\n        const auto base64Size = base64.size();\n        QByteArray result((base64Size * 3) / 4, Qt::Uninitialized);\n        const auto base64result = fromBase64_helper(base64.data(),\n                                                    base64Size,\n                                                    const_cast<char *>(result.constData()),\n                                                    options);\n        result.truncate(int(base64result.decodedLength));\n        return {std::move(result), base64result.status};\n    }\n} // namespace Qt515Base64\n"
  },
  {
    "path": "3rdparty/base64.h",
    "content": "#include <QByteArray>\n\nnamespace Qt515Base64 {\n    enum Base64Option {\n        Base64Encoding = 0,\n        Base64UrlEncoding = 1,\n\n        KeepTrailingEquals = 0,\n        OmitTrailingEquals = 2,\n\n        IgnoreBase64DecodingErrors = 0,\n        AbortOnBase64DecodingErrors = 4,\n    };\n    Q_DECLARE_FLAGS(Base64Options, Base64Option)\n    Q_DECLARE_OPERATORS_FOR_FLAGS(Base64Options)\n\n    enum class Base64DecodingStatus {\n        Ok,\n        IllegalInputLength,\n        IllegalCharacter,\n        IllegalPadding,\n    };\n\n    class FromBase64Result {\n    public:\n        QByteArray decoded;\n        Base64DecodingStatus decodingStatus;\n\n        void swap(FromBase64Result &other) noexcept {\n            qSwap(decoded, other.decoded);\n            qSwap(decodingStatus, other.decodingStatus);\n        }\n\n        explicit operator bool() const noexcept { return decodingStatus == Base64DecodingStatus::Ok; }\n\n#if defined(Q_COMPILER_REF_QUALIFIERS) && !defined(Q_QDOC)\n        QByteArray &operator*() &noexcept { return decoded; }\n        const QByteArray &operator*() const &noexcept { return decoded; }\n        QByteArray &&operator*() &&noexcept { return std::move(decoded); }\n#else\n        QByteArray &operator*() noexcept { return decoded; }\n        const QByteArray &operator*() const noexcept { return decoded; }\n#endif\n    };\n\n    FromBase64Result QByteArray_fromBase64Encoding(const QByteArray &base64, Base64Options options);\n} // namespace Qt515Base64\n"
  },
  {
    "path": "3rdparty/fix_old_qt.h",
    "content": "#pragma once\n\n#include <QString>\n\n#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)\n\ninline QString qEnvironmentVariable(const char *varName) {\n    return qgetenv(varName);\n}\n\n#endif\n"
  },
  {
    "path": "3rdparty/qrcodegen.cpp",
    "content": "/* \n * QR Code generator library (C++)\n * \n * Copyright (c) Project Nayuki. (MIT License)\n * https://www.nayuki.io/page/qr-code-generator-library\n * \n * Permission is hereby granted, free of charge, to any person obtaining a copy of\n * this software and associated documentation files (the \"Software\"), to deal in\n * the Software without restriction, including without limitation the rights to\n * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n * the Software, and to permit persons to whom the Software is furnished to do so,\n * subject to the following conditions:\n * - The above copyright notice and this permission notice shall be included in\n *   all copies or substantial portions of the Software.\n * - The Software is provided \"as is\", without warranty of any kind, express or\n *   implied, including but not limited to the warranties of merchantability,\n *   fitness for a particular purpose and noninfringement. In no event shall the\n *   authors or copyright holders be liable for any claim, damages or other\n *   liability, whether in an action of contract, tort or otherwise, arising from,\n *   out of or in connection with the Software or the use or other dealings in the\n *   Software.\n */\n\n#include <algorithm>\n#include <cassert>\n#include <climits>\n#include <cstddef>\n#include <cstdlib>\n#include <cstring>\n#include <sstream>\n#include <utility>\n#include \"qrcodegen.hpp\"\n\nusing std::int8_t;\nusing std::uint8_t;\nusing std::size_t;\nusing std::vector;\n\n\nnamespace qrcodegen {\n\n/*---- Class QrSegment ----*/\n\nQrSegment::Mode::Mode(int mode, int cc0, int cc1, int cc2) :\n\t\tmodeBits(mode) {\n\tnumBitsCharCount[0] = cc0;\n\tnumBitsCharCount[1] = cc1;\n\tnumBitsCharCount[2] = cc2;\n}\n\n\nint QrSegment::Mode::getModeBits() const {\n\treturn modeBits;\n}\n\n\nint QrSegment::Mode::numCharCountBits(int ver) const {\n\treturn numBitsCharCount[(ver + 7) / 17];\n}\n\n\nconst QrSegment::Mode QrSegment::Mode::NUMERIC     (0x1, 10, 12, 14);\nconst QrSegment::Mode QrSegment::Mode::ALPHANUMERIC(0x2,  9, 11, 13);\nconst QrSegment::Mode QrSegment::Mode::BYTE        (0x4,  8, 16, 16);\nconst QrSegment::Mode QrSegment::Mode::KANJI       (0x8,  8, 10, 12);\nconst QrSegment::Mode QrSegment::Mode::ECI         (0x7,  0,  0,  0);\n\n\nQrSegment QrSegment::makeBytes(const vector<uint8_t> &data) {\n\tif (data.size() > static_cast<unsigned int>(INT_MAX))\n\t\tthrow std::length_error(\"Data too long\");\n\tBitBuffer bb;\n\tfor (uint8_t b : data)\n\t\tbb.appendBits(b, 8);\n\treturn QrSegment(Mode::BYTE, static_cast<int>(data.size()), std::move(bb));\n}\n\n\nQrSegment QrSegment::makeNumeric(const char *digits) {\n\tBitBuffer bb;\n\tint accumData = 0;\n\tint accumCount = 0;\n\tint charCount = 0;\n\tfor (; *digits != '\\0'; digits++, charCount++) {\n\t\tchar c = *digits;\n\t\tif (c < '0' || c > '9')\n\t\t\tthrow std::domain_error(\"String contains non-numeric characters\");\n\t\taccumData = accumData * 10 + (c - '0');\n\t\taccumCount++;\n\t\tif (accumCount == 3) {\n\t\t\tbb.appendBits(static_cast<uint32_t>(accumData), 10);\n\t\t\taccumData = 0;\n\t\t\taccumCount = 0;\n\t\t}\n\t}\n\tif (accumCount > 0)  // 1 or 2 digits remaining\n\t\tbb.appendBits(static_cast<uint32_t>(accumData), accumCount * 3 + 1);\n\treturn QrSegment(Mode::NUMERIC, charCount, std::move(bb));\n}\n\n\nQrSegment QrSegment::makeAlphanumeric(const char *text) {\n\tBitBuffer bb;\n\tint accumData = 0;\n\tint accumCount = 0;\n\tint charCount = 0;\n\tfor (; *text != '\\0'; text++, charCount++) {\n\t\tconst char *temp = std::strchr(ALPHANUMERIC_CHARSET, *text);\n\t\tif (temp == nullptr)\n\t\t\tthrow std::domain_error(\"String contains unencodable characters in alphanumeric mode\");\n\t\taccumData = accumData * 45 + static_cast<int>(temp - ALPHANUMERIC_CHARSET);\n\t\taccumCount++;\n\t\tif (accumCount == 2) {\n\t\t\tbb.appendBits(static_cast<uint32_t>(accumData), 11);\n\t\t\taccumData = 0;\n\t\t\taccumCount = 0;\n\t\t}\n\t}\n\tif (accumCount > 0)  // 1 character remaining\n\t\tbb.appendBits(static_cast<uint32_t>(accumData), 6);\n\treturn QrSegment(Mode::ALPHANUMERIC, charCount, std::move(bb));\n}\n\n\nvector<QrSegment> QrSegment::makeSegments(const char *text) {\n\t// Select the most efficient segment encoding automatically\n\tvector<QrSegment> result;\n\tif (*text == '\\0');  // Leave result empty\n\telse if (isNumeric(text))\n\t\tresult.push_back(makeNumeric(text));\n\telse if (isAlphanumeric(text))\n\t\tresult.push_back(makeAlphanumeric(text));\n\telse {\n\t\tvector<uint8_t> bytes;\n\t\tfor (; *text != '\\0'; text++)\n\t\t\tbytes.push_back(static_cast<uint8_t>(*text));\n\t\tresult.push_back(makeBytes(bytes));\n\t}\n\treturn result;\n}\n\n\nQrSegment QrSegment::makeEci(long assignVal) {\n\tBitBuffer bb;\n\tif (assignVal < 0)\n\t\tthrow std::domain_error(\"ECI assignment value out of range\");\n\telse if (assignVal < (1 << 7))\n\t\tbb.appendBits(static_cast<uint32_t>(assignVal), 8);\n\telse if (assignVal < (1 << 14)) {\n\t\tbb.appendBits(2, 2);\n\t\tbb.appendBits(static_cast<uint32_t>(assignVal), 14);\n\t} else if (assignVal < 1000000L) {\n\t\tbb.appendBits(6, 3);\n\t\tbb.appendBits(static_cast<uint32_t>(assignVal), 21);\n\t} else\n\t\tthrow std::domain_error(\"ECI assignment value out of range\");\n\treturn QrSegment(Mode::ECI, 0, std::move(bb));\n}\n\n\nQrSegment::QrSegment(const Mode &md, int numCh, const std::vector<bool> &dt) :\n\t\tmode(&md),\n\t\tnumChars(numCh),\n\t\tdata(dt) {\n\tif (numCh < 0)\n\t\tthrow std::domain_error(\"Invalid value\");\n}\n\n\nQrSegment::QrSegment(const Mode &md, int numCh, std::vector<bool> &&dt) :\n\t\tmode(&md),\n\t\tnumChars(numCh),\n\t\tdata(std::move(dt)) {\n\tif (numCh < 0)\n\t\tthrow std::domain_error(\"Invalid value\");\n}\n\n\nint QrSegment::getTotalBits(const vector<QrSegment> &segs, int version) {\n\tint result = 0;\n\tfor (const QrSegment &seg : segs) {\n\t\tint ccbits = seg.mode->numCharCountBits(version);\n\t\tif (seg.numChars >= (1L << ccbits))\n\t\t\treturn -1;  // The segment's length doesn't fit the field's bit width\n\t\tif (4 + ccbits > INT_MAX - result)\n\t\t\treturn -1;  // The sum will overflow an int type\n\t\tresult += 4 + ccbits;\n\t\tif (seg.data.size() > static_cast<unsigned int>(INT_MAX - result))\n\t\t\treturn -1;  // The sum will overflow an int type\n\t\tresult += static_cast<int>(seg.data.size());\n\t}\n\treturn result;\n}\n\n\nbool QrSegment::isNumeric(const char *text) {\n\tfor (; *text != '\\0'; text++) {\n\t\tchar c = *text;\n\t\tif (c < '0' || c > '9')\n\t\t\treturn false;\n\t}\n\treturn true;\n}\n\n\nbool QrSegment::isAlphanumeric(const char *text) {\n\tfor (; *text != '\\0'; text++) {\n\t\tif (std::strchr(ALPHANUMERIC_CHARSET, *text) == nullptr)\n\t\t\treturn false;\n\t}\n\treturn true;\n}\n\n\nconst QrSegment::Mode &QrSegment::getMode() const {\n\treturn *mode;\n}\n\n\nint QrSegment::getNumChars() const {\n\treturn numChars;\n}\n\n\nconst std::vector<bool> &QrSegment::getData() const {\n\treturn data;\n}\n\n\nconst char *QrSegment::ALPHANUMERIC_CHARSET = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:\";\n\n\n\n/*---- Class QrCode ----*/\n\nint QrCode::getFormatBits(Ecc ecl) {\n\tswitch (ecl) {\n\t\tcase Ecc::LOW     :  return 1;\n\t\tcase Ecc::MEDIUM  :  return 0;\n\t\tcase Ecc::QUARTILE:  return 3;\n\t\tcase Ecc::HIGH    :  return 2;\n\t\tdefault:  throw std::logic_error(\"Unreachable\");\n\t}\n}\n\n\nQrCode QrCode::encodeText(const char *text, Ecc ecl) {\n\tvector<QrSegment> segs = QrSegment::makeSegments(text);\n\treturn encodeSegments(segs, ecl);\n}\n\n\nQrCode QrCode::encodeBinary(const vector<uint8_t> &data, Ecc ecl) {\n\tvector<QrSegment> segs{QrSegment::makeBytes(data)};\n\treturn encodeSegments(segs, ecl);\n}\n\n\nQrCode QrCode::encodeSegments(const vector<QrSegment> &segs, Ecc ecl,\n\t\tint minVersion, int maxVersion, int mask, bool boostEcl) {\n\tif (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7)\n\t\tthrow std::invalid_argument(\"Invalid value\");\n\t\n\t// Find the minimal version number to use\n\tint version, dataUsedBits;\n\tfor (version = minVersion; ; version++) {\n\t\tint dataCapacityBits = getNumDataCodewords(version, ecl) * 8;  // Number of data bits available\n\t\tdataUsedBits = QrSegment::getTotalBits(segs, version);\n\t\tif (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits)\n\t\t\tbreak;  // This version number is found to be suitable\n\t\tif (version >= maxVersion) {  // All versions in the range could not fit the given data\n\t\t\tstd::ostringstream sb;\n\t\t\tif (dataUsedBits == -1)\n\t\t\t\tsb << \"Segment too long\";\n\t\t\telse {\n\t\t\t\tsb << \"Data length = \" << dataUsedBits << \" bits, \";\n\t\t\t\tsb << \"Max capacity = \" << dataCapacityBits << \" bits\";\n\t\t\t}\n\t\t\tthrow data_too_long(sb.str());\n\t\t}\n\t}\n\tassert(dataUsedBits != -1);\n\t\n\t// Increase the error correction level while the data still fits in the current version number\n\tfor (Ecc newEcl : {Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) {  // From low to high\n\t\tif (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8)\n\t\t\tecl = newEcl;\n\t}\n\t\n\t// Concatenate all segments to create the data bit string\n\tBitBuffer bb;\n\tfor (const QrSegment &seg : segs) {\n\t\tbb.appendBits(static_cast<uint32_t>(seg.getMode().getModeBits()), 4);\n\t\tbb.appendBits(static_cast<uint32_t>(seg.getNumChars()), seg.getMode().numCharCountBits(version));\n\t\tbb.insert(bb.end(), seg.getData().begin(), seg.getData().end());\n\t}\n\tassert(bb.size() == static_cast<unsigned int>(dataUsedBits));\n\t\n\t// Add terminator and pad up to a byte if applicable\n\tsize_t dataCapacityBits = static_cast<size_t>(getNumDataCodewords(version, ecl)) * 8;\n\tassert(bb.size() <= dataCapacityBits);\n\tbb.appendBits(0, std::min(4, static_cast<int>(dataCapacityBits - bb.size())));\n\tbb.appendBits(0, (8 - static_cast<int>(bb.size() % 8)) % 8);\n\tassert(bb.size() % 8 == 0);\n\t\n\t// Pad with alternating bytes until data capacity is reached\n\tfor (uint8_t padByte = 0xEC; bb.size() < dataCapacityBits; padByte ^= 0xEC ^ 0x11)\n\t\tbb.appendBits(padByte, 8);\n\t\n\t// Pack bits into bytes in big endian\n\tvector<uint8_t> dataCodewords(bb.size() / 8);\n\tfor (size_t i = 0; i < bb.size(); i++)\n\t\tdataCodewords.at(i >> 3) |= (bb.at(i) ? 1 : 0) << (7 - (i & 7));\n\t\n\t// Create the QR Code object\n\treturn QrCode(version, ecl, dataCodewords, mask);\n}\n\n\nQrCode::QrCode(int ver, Ecc ecl, const vector<uint8_t> &dataCodewords, int msk) :\n\t\t// Initialize fields and check arguments\n\t\tversion(ver),\n\t\terrorCorrectionLevel(ecl) {\n\tif (ver < MIN_VERSION || ver > MAX_VERSION)\n\t\tthrow std::domain_error(\"Version value out of range\");\n\tif (msk < -1 || msk > 7)\n\t\tthrow std::domain_error(\"Mask value out of range\");\n\tsize = ver * 4 + 17;\n\tsize_t sz = static_cast<size_t>(size);\n\tmodules    = vector<vector<bool> >(sz, vector<bool>(sz));  // Initially all light\n\tisFunction = vector<vector<bool> >(sz, vector<bool>(sz));\n\t\n\t// Compute ECC, draw modules\n\tdrawFunctionPatterns();\n\tconst vector<uint8_t> allCodewords = addEccAndInterleave(dataCodewords);\n\tdrawCodewords(allCodewords);\n\t\n\t// Do masking\n\tif (msk == -1) {  // Automatically choose best mask\n\t\tlong minPenalty = LONG_MAX;\n\t\tfor (int i = 0; i < 8; i++) {\n\t\t\tapplyMask(i);\n\t\t\tdrawFormatBits(i);\n\t\t\tlong penalty = getPenaltyScore();\n\t\t\tif (penalty < minPenalty) {\n\t\t\t\tmsk = i;\n\t\t\t\tminPenalty = penalty;\n\t\t\t}\n\t\t\tapplyMask(i);  // Undoes the mask due to XOR\n\t\t}\n\t}\n\tassert(0 <= msk && msk <= 7);\n\tmask = msk;\n\tapplyMask(msk);  // Apply the final choice of mask\n\tdrawFormatBits(msk);  // Overwrite old format bits\n\t\n\tisFunction.clear();\n\tisFunction.shrink_to_fit();\n}\n\n\nint QrCode::getVersion() const {\n\treturn version;\n}\n\n\nint QrCode::getSize() const {\n\treturn size;\n}\n\n\nQrCode::Ecc QrCode::getErrorCorrectionLevel() const {\n\treturn errorCorrectionLevel;\n}\n\n\nint QrCode::getMask() const {\n\treturn mask;\n}\n\n\nbool QrCode::getModule(int x, int y) const {\n\treturn 0 <= x && x < size && 0 <= y && y < size && module(x, y);\n}\n\n\nvoid QrCode::drawFunctionPatterns() {\n\t// Draw horizontal and vertical timing patterns\n\tfor (int i = 0; i < size; i++) {\n\t\tsetFunctionModule(6, i, i % 2 == 0);\n\t\tsetFunctionModule(i, 6, i % 2 == 0);\n\t}\n\t\n\t// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)\n\tdrawFinderPattern(3, 3);\n\tdrawFinderPattern(size - 4, 3);\n\tdrawFinderPattern(3, size - 4);\n\t\n\t// Draw numerous alignment patterns\n\tconst vector<int> alignPatPos = getAlignmentPatternPositions();\n\tsize_t numAlign = alignPatPos.size();\n\tfor (size_t i = 0; i < numAlign; i++) {\n\t\tfor (size_t j = 0; j < numAlign; j++) {\n\t\t\t// Don't draw on the three finder corners\n\t\t\tif (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)))\n\t\t\t\tdrawAlignmentPattern(alignPatPos.at(i), alignPatPos.at(j));\n\t\t}\n\t}\n\t\n\t// Draw configuration data\n\tdrawFormatBits(0);  // Dummy mask value; overwritten later in the constructor\n\tdrawVersion();\n}\n\n\nvoid QrCode::drawFormatBits(int msk) {\n\t// Calculate error correction code and pack bits\n\tint data = getFormatBits(errorCorrectionLevel) << 3 | msk;  // errCorrLvl is uint2, msk is uint3\n\tint rem = data;\n\tfor (int i = 0; i < 10; i++)\n\t\trem = (rem << 1) ^ ((rem >> 9) * 0x537);\n\tint bits = (data << 10 | rem) ^ 0x5412;  // uint15\n\tassert(bits >> 15 == 0);\n\t\n\t// Draw first copy\n\tfor (int i = 0; i <= 5; i++)\n\t\tsetFunctionModule(8, i, getBit(bits, i));\n\tsetFunctionModule(8, 7, getBit(bits, 6));\n\tsetFunctionModule(8, 8, getBit(bits, 7));\n\tsetFunctionModule(7, 8, getBit(bits, 8));\n\tfor (int i = 9; i < 15; i++)\n\t\tsetFunctionModule(14 - i, 8, getBit(bits, i));\n\t\n\t// Draw second copy\n\tfor (int i = 0; i < 8; i++)\n\t\tsetFunctionModule(size - 1 - i, 8, getBit(bits, i));\n\tfor (int i = 8; i < 15; i++)\n\t\tsetFunctionModule(8, size - 15 + i, getBit(bits, i));\n\tsetFunctionModule(8, size - 8, true);  // Always dark\n}\n\n\nvoid QrCode::drawVersion() {\n\tif (version < 7)\n\t\treturn;\n\t\n\t// Calculate error correction code and pack bits\n\tint rem = version;  // version is uint6, in the range [7, 40]\n\tfor (int i = 0; i < 12; i++)\n\t\trem = (rem << 1) ^ ((rem >> 11) * 0x1F25);\n\tlong bits = static_cast<long>(version) << 12 | rem;  // uint18\n\tassert(bits >> 18 == 0);\n\t\n\t// Draw two copies\n\tfor (int i = 0; i < 18; i++) {\n\t\tbool bit = getBit(bits, i);\n\t\tint a = size - 11 + i % 3;\n\t\tint b = i / 3;\n\t\tsetFunctionModule(a, b, bit);\n\t\tsetFunctionModule(b, a, bit);\n\t}\n}\n\n\nvoid QrCode::drawFinderPattern(int x, int y) {\n\tfor (int dy = -4; dy <= 4; dy++) {\n\t\tfor (int dx = -4; dx <= 4; dx++) {\n\t\t\tint dist = std::max(std::abs(dx), std::abs(dy));  // Chebyshev/infinity norm\n\t\t\tint xx = x + dx, yy = y + dy;\n\t\t\tif (0 <= xx && xx < size && 0 <= yy && yy < size)\n\t\t\t\tsetFunctionModule(xx, yy, dist != 2 && dist != 4);\n\t\t}\n\t}\n}\n\n\nvoid QrCode::drawAlignmentPattern(int x, int y) {\n\tfor (int dy = -2; dy <= 2; dy++) {\n\t\tfor (int dx = -2; dx <= 2; dx++)\n\t\t\tsetFunctionModule(x + dx, y + dy, std::max(std::abs(dx), std::abs(dy)) != 1);\n\t}\n}\n\n\nvoid QrCode::setFunctionModule(int x, int y, bool isDark) {\n\tsize_t ux = static_cast<size_t>(x);\n\tsize_t uy = static_cast<size_t>(y);\n\tmodules   .at(uy).at(ux) = isDark;\n\tisFunction.at(uy).at(ux) = true;\n}\n\n\nbool QrCode::module(int x, int y) const {\n\treturn modules.at(static_cast<size_t>(y)).at(static_cast<size_t>(x));\n}\n\n\nvector<uint8_t> QrCode::addEccAndInterleave(const vector<uint8_t> &data) const {\n\tif (data.size() != static_cast<unsigned int>(getNumDataCodewords(version, errorCorrectionLevel)))\n\t\tthrow std::invalid_argument(\"Invalid argument\");\n\t\n\t// Calculate parameter numbers\n\tint numBlocks = NUM_ERROR_CORRECTION_BLOCKS[static_cast<int>(errorCorrectionLevel)][version];\n\tint blockEccLen = ECC_CODEWORDS_PER_BLOCK  [static_cast<int>(errorCorrectionLevel)][version];\n\tint rawCodewords = getNumRawDataModules(version) / 8;\n\tint numShortBlocks = numBlocks - rawCodewords % numBlocks;\n\tint shortBlockLen = rawCodewords / numBlocks;\n\t\n\t// Split data into blocks and append ECC to each block\n\tvector<vector<uint8_t> > blocks;\n\tconst vector<uint8_t> rsDiv = reedSolomonComputeDivisor(blockEccLen);\n\tfor (int i = 0, k = 0; i < numBlocks; i++) {\n\t\tvector<uint8_t> dat(data.cbegin() + k, data.cbegin() + (k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1)));\n\t\tk += static_cast<int>(dat.size());\n\t\tconst vector<uint8_t> ecc = reedSolomonComputeRemainder(dat, rsDiv);\n\t\tif (i < numShortBlocks)\n\t\t\tdat.push_back(0);\n\t\tdat.insert(dat.end(), ecc.cbegin(), ecc.cend());\n\t\tblocks.push_back(std::move(dat));\n\t}\n\t\n\t// Interleave (not concatenate) the bytes from every block into a single sequence\n\tvector<uint8_t> result;\n\tfor (size_t i = 0; i < blocks.at(0).size(); i++) {\n\t\tfor (size_t j = 0; j < blocks.size(); j++) {\n\t\t\t// Skip the padding byte in short blocks\n\t\t\tif (i != static_cast<unsigned int>(shortBlockLen - blockEccLen) || j >= static_cast<unsigned int>(numShortBlocks))\n\t\t\t\tresult.push_back(blocks.at(j).at(i));\n\t\t}\n\t}\n\tassert(result.size() == static_cast<unsigned int>(rawCodewords));\n\treturn result;\n}\n\n\nvoid QrCode::drawCodewords(const vector<uint8_t> &data) {\n\tif (data.size() != static_cast<unsigned int>(getNumRawDataModules(version) / 8))\n\t\tthrow std::invalid_argument(\"Invalid argument\");\n\t\n\tsize_t i = 0;  // Bit index into the data\n\t// Do the funny zigzag scan\n\tfor (int right = size - 1; right >= 1; right -= 2) {  // Index of right column in each column pair\n\t\tif (right == 6)\n\t\t\tright = 5;\n\t\tfor (int vert = 0; vert < size; vert++) {  // Vertical counter\n\t\t\tfor (int j = 0; j < 2; j++) {\n\t\t\t\tsize_t x = static_cast<size_t>(right - j);  // Actual x coordinate\n\t\t\t\tbool upward = ((right + 1) & 2) == 0;\n\t\t\t\tsize_t y = static_cast<size_t>(upward ? size - 1 - vert : vert);  // Actual y coordinate\n\t\t\t\tif (!isFunction.at(y).at(x) && i < data.size() * 8) {\n\t\t\t\t\tmodules.at(y).at(x) = getBit(data.at(i >> 3), 7 - static_cast<int>(i & 7));\n\t\t\t\t\ti++;\n\t\t\t\t}\n\t\t\t\t// If this QR Code has any remainder bits (0 to 7), they were assigned as\n\t\t\t\t// 0/false/light by the constructor and are left unchanged by this method\n\t\t\t}\n\t\t}\n\t}\n\tassert(i == data.size() * 8);\n}\n\n\nvoid QrCode::applyMask(int msk) {\n\tif (msk < 0 || msk > 7)\n\t\tthrow std::domain_error(\"Mask value out of range\");\n\tsize_t sz = static_cast<size_t>(size);\n\tfor (size_t y = 0; y < sz; y++) {\n\t\tfor (size_t x = 0; x < sz; x++) {\n\t\t\tbool invert;\n\t\t\tswitch (msk) {\n\t\t\t\tcase 0:  invert = (x + y) % 2 == 0;                    break;\n\t\t\t\tcase 1:  invert = y % 2 == 0;                          break;\n\t\t\t\tcase 2:  invert = x % 3 == 0;                          break;\n\t\t\t\tcase 3:  invert = (x + y) % 3 == 0;                    break;\n\t\t\t\tcase 4:  invert = (x / 3 + y / 2) % 2 == 0;            break;\n\t\t\t\tcase 5:  invert = x * y % 2 + x * y % 3 == 0;          break;\n\t\t\t\tcase 6:  invert = (x * y % 2 + x * y % 3) % 2 == 0;    break;\n\t\t\t\tcase 7:  invert = ((x + y) % 2 + x * y % 3) % 2 == 0;  break;\n\t\t\t\tdefault:  throw std::logic_error(\"Unreachable\");\n\t\t\t}\n\t\t\tmodules.at(y).at(x) = modules.at(y).at(x) ^ (invert & !isFunction.at(y).at(x));\n\t\t}\n\t}\n}\n\n\nlong QrCode::getPenaltyScore() const {\n\tlong result = 0;\n\t\n\t// Adjacent modules in row having same color, and finder-like patterns\n\tfor (int y = 0; y < size; y++) {\n\t\tbool runColor = false;\n\t\tint runX = 0;\n\t\tstd::array<int,7> runHistory = {};\n\t\tfor (int x = 0; x < size; x++) {\n\t\t\tif (module(x, y) == runColor) {\n\t\t\t\trunX++;\n\t\t\t\tif (runX == 5)\n\t\t\t\t\tresult += PENALTY_N1;\n\t\t\t\telse if (runX > 5)\n\t\t\t\t\tresult++;\n\t\t\t} else {\n\t\t\t\tfinderPenaltyAddHistory(runX, runHistory);\n\t\t\t\tif (!runColor)\n\t\t\t\t\tresult += finderPenaltyCountPatterns(runHistory) * PENALTY_N3;\n\t\t\t\trunColor = module(x, y);\n\t\t\t\trunX = 1;\n\t\t\t}\n\t\t}\n\t\tresult += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3;\n\t}\n\t// Adjacent modules in column having same color, and finder-like patterns\n\tfor (int x = 0; x < size; x++) {\n\t\tbool runColor = false;\n\t\tint runY = 0;\n\t\tstd::array<int,7> runHistory = {};\n\t\tfor (int y = 0; y < size; y++) {\n\t\t\tif (module(x, y) == runColor) {\n\t\t\t\trunY++;\n\t\t\t\tif (runY == 5)\n\t\t\t\t\tresult += PENALTY_N1;\n\t\t\t\telse if (runY > 5)\n\t\t\t\t\tresult++;\n\t\t\t} else {\n\t\t\t\tfinderPenaltyAddHistory(runY, runHistory);\n\t\t\t\tif (!runColor)\n\t\t\t\t\tresult += finderPenaltyCountPatterns(runHistory) * PENALTY_N3;\n\t\t\t\trunColor = module(x, y);\n\t\t\t\trunY = 1;\n\t\t\t}\n\t\t}\n\t\tresult += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3;\n\t}\n\t\n\t// 2*2 blocks of modules having same color\n\tfor (int y = 0; y < size - 1; y++) {\n\t\tfor (int x = 0; x < size - 1; x++) {\n\t\t\tbool  color = module(x, y);\n\t\t\tif (  color == module(x + 1, y) &&\n\t\t\t      color == module(x, y + 1) &&\n\t\t\t      color == module(x + 1, y + 1))\n\t\t\t\tresult += PENALTY_N2;\n\t\t}\n\t}\n\t\n\t// Balance of dark and light modules\n\tint dark = 0;\n\tfor (const vector<bool> &row : modules) {\n\t\tfor (bool color : row) {\n\t\t\tif (color)\n\t\t\t\tdark++;\n\t\t}\n\t}\n\tint total = size * size;  // Note that size is odd, so dark/total != 1/2\n\t// Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)%\n\tint k = static_cast<int>((std::abs(dark * 20L - total * 10L) + total - 1) / total) - 1;\n\tassert(0 <= k && k <= 9);\n\tresult += k * PENALTY_N4;\n\tassert(0 <= result && result <= 2568888L);  // Non-tight upper bound based on default values of PENALTY_N1, ..., N4\n\treturn result;\n}\n\n\nvector<int> QrCode::getAlignmentPatternPositions() const {\n\tif (version == 1)\n\t\treturn vector<int>();\n\telse {\n\t\tint numAlign = version / 7 + 2;\n\t\tint step = (version == 32) ? 26 :\n\t\t\t(version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2;\n\t\tvector<int> result;\n\t\tfor (int i = 0, pos = size - 7; i < numAlign - 1; i++, pos -= step)\n\t\t\tresult.insert(result.begin(), pos);\n\t\tresult.insert(result.begin(), 6);\n\t\treturn result;\n\t}\n}\n\n\nint QrCode::getNumRawDataModules(int ver) {\n\tif (ver < MIN_VERSION || ver > MAX_VERSION)\n\t\tthrow std::domain_error(\"Version number out of range\");\n\tint result = (16 * ver + 128) * ver + 64;\n\tif (ver >= 2) {\n\t\tint numAlign = ver / 7 + 2;\n\t\tresult -= (25 * numAlign - 10) * numAlign - 55;\n\t\tif (ver >= 7)\n\t\t\tresult -= 36;\n\t}\n\tassert(208 <= result && result <= 29648);\n\treturn result;\n}\n\n\nint QrCode::getNumDataCodewords(int ver, Ecc ecl) {\n\treturn getNumRawDataModules(ver) / 8\n\t\t- ECC_CODEWORDS_PER_BLOCK    [static_cast<int>(ecl)][ver]\n\t\t* NUM_ERROR_CORRECTION_BLOCKS[static_cast<int>(ecl)][ver];\n}\n\n\nvector<uint8_t> QrCode::reedSolomonComputeDivisor(int degree) {\n\tif (degree < 1 || degree > 255)\n\t\tthrow std::domain_error(\"Degree out of range\");\n\t// Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1.\n\t// For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}.\n\tvector<uint8_t> result(static_cast<size_t>(degree));\n\tresult.at(result.size() - 1) = 1;  // Start off with the monomial x^0\n\t\n\t// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),\n\t// and drop the highest monomial term which is always 1x^degree.\n\t// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).\n\tuint8_t root = 1;\n\tfor (int i = 0; i < degree; i++) {\n\t\t// Multiply the current product by (x - r^i)\n\t\tfor (size_t j = 0; j < result.size(); j++) {\n\t\t\tresult.at(j) = reedSolomonMultiply(result.at(j), root);\n\t\t\tif (j + 1 < result.size())\n\t\t\t\tresult.at(j) ^= result.at(j + 1);\n\t\t}\n\t\troot = reedSolomonMultiply(root, 0x02);\n\t}\n\treturn result;\n}\n\n\nvector<uint8_t> QrCode::reedSolomonComputeRemainder(const vector<uint8_t> &data, const vector<uint8_t> &divisor) {\n\tvector<uint8_t> result(divisor.size());\n\tfor (uint8_t b : data) {  // Polynomial division\n\t\tuint8_t factor = b ^ result.at(0);\n\t\tresult.erase(result.begin());\n\t\tresult.push_back(0);\n\t\tfor (size_t i = 0; i < result.size(); i++)\n\t\t\tresult.at(i) ^= reedSolomonMultiply(divisor.at(i), factor);\n\t}\n\treturn result;\n}\n\n\nuint8_t QrCode::reedSolomonMultiply(uint8_t x, uint8_t y) {\n\t// Russian peasant multiplication\n\tint z = 0;\n\tfor (int i = 7; i >= 0; i--) {\n\t\tz = (z << 1) ^ ((z >> 7) * 0x11D);\n\t\tz ^= ((y >> i) & 1) * x;\n\t}\n\tassert(z >> 8 == 0);\n\treturn static_cast<uint8_t>(z);\n}\n\n\nint QrCode::finderPenaltyCountPatterns(const std::array<int,7> &runHistory) const {\n\tint n = runHistory.at(1);\n\tassert(n <= size * 3);\n\tbool core = n > 0 && runHistory.at(2) == n && runHistory.at(3) == n * 3 && runHistory.at(4) == n && runHistory.at(5) == n;\n\treturn (core && runHistory.at(0) >= n * 4 && runHistory.at(6) >= n ? 1 : 0)\n\t     + (core && runHistory.at(6) >= n * 4 && runHistory.at(0) >= n ? 1 : 0);\n}\n\n\nint QrCode::finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array<int,7> &runHistory) const {\n\tif (currentRunColor) {  // Terminate dark run\n\t\tfinderPenaltyAddHistory(currentRunLength, runHistory);\n\t\tcurrentRunLength = 0;\n\t}\n\tcurrentRunLength += size;  // Add light border to final run\n\tfinderPenaltyAddHistory(currentRunLength, runHistory);\n\treturn finderPenaltyCountPatterns(runHistory);\n}\n\n\nvoid QrCode::finderPenaltyAddHistory(int currentRunLength, std::array<int,7> &runHistory) const {\n\tif (runHistory.at(0) == 0)\n\t\tcurrentRunLength += size;  // Add light border to initial run\n\tstd::copy_backward(runHistory.cbegin(), runHistory.cend() - 1, runHistory.end());\n\trunHistory.at(0) = currentRunLength;\n}\n\n\nbool QrCode::getBit(long x, int i) {\n\treturn ((x >> i) & 1) != 0;\n}\n\n\n/*---- Tables of constants ----*/\n\nconst int QrCode::PENALTY_N1 =  3;\nconst int QrCode::PENALTY_N2 =  3;\nconst int QrCode::PENALTY_N3 = 40;\nconst int QrCode::PENALTY_N4 = 10;\n\n\nconst int8_t QrCode::ECC_CODEWORDS_PER_BLOCK[4][41] = {\n\t// Version: (note that index 0 is for padding, and is set to an illegal value)\n\t//0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40    Error correction level\n\t{-1,  7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30},  // Low\n\t{-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28},  // Medium\n\t{-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30},  // Quartile\n\t{-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30},  // High\n};\n\nconst int8_t QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = {\n\t// Version: (note that index 0 is for padding, and is set to an illegal value)\n\t//0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40    Error correction level\n\t{-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4,  4,  4,  4,  4,  6,  6,  6,  6,  7,  8,  8,  9,  9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25},  // Low\n\t{-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5,  5,  8,  9,  9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49},  // Medium\n\t{-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8,  8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68},  // Quartile\n\t{-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81},  // High\n};\n\n\ndata_too_long::data_too_long(const std::string &msg) :\n\tstd::length_error(msg) {}\n\n\n\n/*---- Class BitBuffer ----*/\n\nBitBuffer::BitBuffer()\n\t: std::vector<bool>() {}\n\n\nvoid BitBuffer::appendBits(std::uint32_t val, int len) {\n\tif (len < 0 || len > 31 || val >> len != 0)\n\t\tthrow std::domain_error(\"Value out of range\");\n\tfor (int i = len - 1; i >= 0; i--)  // Append bit by bit\n\t\tthis->push_back(((val >> i) & 1) != 0);\n}\n\n}\n"
  },
  {
    "path": "3rdparty/qrcodegen.hpp",
    "content": "/* \n * QR Code generator library (C++)\n * \n * Copyright (c) Project Nayuki. (MIT License)\n * https://www.nayuki.io/page/qr-code-generator-library\n * \n * Permission is hereby granted, free of charge, to any person obtaining a copy of\n * this software and associated documentation files (the \"Software\"), to deal in\n * the Software without restriction, including without limitation the rights to\n * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n * the Software, and to permit persons to whom the Software is furnished to do so,\n * subject to the following conditions:\n * - The above copyright notice and this permission notice shall be included in\n *   all copies or substantial portions of the Software.\n * - The Software is provided \"as is\", without warranty of any kind, express or\n *   implied, including but not limited to the warranties of merchantability,\n *   fitness for a particular purpose and noninfringement. In no event shall the\n *   authors or copyright holders be liable for any claim, damages or other\n *   liability, whether in an action of contract, tort or otherwise, arising from,\n *   out of or in connection with the Software or the use or other dealings in the\n *   Software.\n */\n\n#pragma once\n\n#include <array>\n#include <cstdint>\n#include <stdexcept>\n#include <string>\n#include <vector>\n\n\nnamespace qrcodegen {\n\n/* \n * A segment of character/binary/control data in a QR Code symbol.\n * Instances of this class are immutable.\n * The mid-level way to create a segment is to take the payload data\n * and call a static factory function such as QrSegment::makeNumeric().\n * The low-level way to create a segment is to custom-make the bit buffer\n * and call the QrSegment() constructor with appropriate values.\n * This segment class imposes no length restrictions, but QR Codes have restrictions.\n * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.\n * Any segment longer than this is meaningless for the purpose of generating QR Codes.\n */\nclass QrSegment final {\n\t\n\t/*---- Public helper enumeration ----*/\n\t\n\t/* \n\t * Describes how a segment's data bits are interpreted. Immutable.\n\t */\n\tpublic: class Mode final {\n\t\t\n\t\t/*-- Constants --*/\n\t\t\n\t\tpublic: static const Mode NUMERIC;\n\t\tpublic: static const Mode ALPHANUMERIC;\n\t\tpublic: static const Mode BYTE;\n\t\tpublic: static const Mode KANJI;\n\t\tpublic: static const Mode ECI;\n\t\t\n\t\t\n\t\t/*-- Fields --*/\n\t\t\n\t\t// The mode indicator bits, which is a uint4 value (range 0 to 15).\n\t\tprivate: int modeBits;\n\t\t\n\t\t// Number of character count bits for three different version ranges.\n\t\tprivate: int numBitsCharCount[3];\n\t\t\n\t\t\n\t\t/*-- Constructor --*/\n\t\t\n\t\tprivate: Mode(int mode, int cc0, int cc1, int cc2);\n\t\t\n\t\t\n\t\t/*-- Methods --*/\n\t\t\n\t\t/* \n\t\t * (Package-private) Returns the mode indicator bits, which is an unsigned 4-bit value (range 0 to 15).\n\t\t */\n\t\tpublic: int getModeBits() const;\n\t\t\n\t\t/* \n\t\t * (Package-private) Returns the bit width of the character count field for a segment in\n\t\t * this mode in a QR Code at the given version number. The result is in the range [0, 16].\n\t\t */\n\t\tpublic: int numCharCountBits(int ver) const;\n\t\t\n\t};\n\t\n\t\n\t\n\t/*---- Static factory functions (mid level) ----*/\n\t\n\t/* \n\t * Returns a segment representing the given binary data encoded in\n\t * byte mode. All input byte vectors are acceptable. Any text string\n\t * can be converted to UTF-8 bytes and encoded as a byte mode segment.\n\t */\n\tpublic: static QrSegment makeBytes(const std::vector<std::uint8_t> &data);\n\t\n\t\n\t/* \n\t * Returns a segment representing the given string of decimal digits encoded in numeric mode.\n\t */\n\tpublic: static QrSegment makeNumeric(const char *digits);\n\t\n\t\n\t/* \n\t * Returns a segment representing the given text string encoded in alphanumeric mode.\n\t * The characters allowed are: 0 to 9, A to Z (uppercase only), space,\n\t * dollar, percent, asterisk, plus, hyphen, period, slash, colon.\n\t */\n\tpublic: static QrSegment makeAlphanumeric(const char *text);\n\t\n\t\n\t/* \n\t * Returns a list of zero or more segments to represent the given text string. The result\n\t * may use various segment modes and switch modes to optimize the length of the bit stream.\n\t */\n\tpublic: static std::vector<QrSegment> makeSegments(const char *text);\n\t\n\t\n\t/* \n\t * Returns a segment representing an Extended Channel Interpretation\n\t * (ECI) designator with the given assignment value.\n\t */\n\tpublic: static QrSegment makeEci(long assignVal);\n\t\n\t\n\t/*---- Public static helper functions ----*/\n\t\n\t/* \n\t * Tests whether the given string can be encoded as a segment in numeric mode.\n\t * A string is encodable iff each character is in the range 0 to 9.\n\t */\n\tpublic: static bool isNumeric(const char *text);\n\t\n\t\n\t/* \n\t * Tests whether the given string can be encoded as a segment in alphanumeric mode.\n\t * A string is encodable iff each character is in the following set: 0 to 9, A to Z\n\t * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.\n\t */\n\tpublic: static bool isAlphanumeric(const char *text);\n\t\n\t\n\t\n\t/*---- Instance fields ----*/\n\t\n\t/* The mode indicator of this segment. Accessed through getMode(). */\n\tprivate: const Mode *mode;\n\t\n\t/* The length of this segment's unencoded data. Measured in characters for\n\t * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.\n\t * Always zero or positive. Not the same as the data's bit length.\n\t * Accessed through getNumChars(). */\n\tprivate: int numChars;\n\t\n\t/* The data bits of this segment. Accessed through getData(). */\n\tprivate: std::vector<bool> data;\n\t\n\t\n\t/*---- Constructors (low level) ----*/\n\t\n\t/* \n\t * Creates a new QR Code segment with the given attributes and data.\n\t * The character count (numCh) must agree with the mode and the bit buffer length,\n\t * but the constraint isn't checked. The given bit buffer is copied and stored.\n\t */\n\tpublic: QrSegment(const Mode &md, int numCh, const std::vector<bool> &dt);\n\t\n\t\n\t/* \n\t * Creates a new QR Code segment with the given parameters and data.\n\t * The character count (numCh) must agree with the mode and the bit buffer length,\n\t * but the constraint isn't checked. The given bit buffer is moved and stored.\n\t */\n\tpublic: QrSegment(const Mode &md, int numCh, std::vector<bool> &&dt);\n\t\n\t\n\t/*---- Methods ----*/\n\t\n\t/* \n\t * Returns the mode field of this segment.\n\t */\n\tpublic: const Mode &getMode() const;\n\t\n\t\n\t/* \n\t * Returns the character count field of this segment.\n\t */\n\tpublic: int getNumChars() const;\n\t\n\t\n\t/* \n\t * Returns the data bits of this segment.\n\t */\n\tpublic: const std::vector<bool> &getData() const;\n\t\n\t\n\t// (Package-private) Calculates the number of bits needed to encode the given segments at\n\t// the given version. Returns a non-negative number if successful. Otherwise returns -1 if a\n\t// segment has too many characters to fit its length field, or the total bits exceeds INT_MAX.\n\tpublic: static int getTotalBits(const std::vector<QrSegment> &segs, int version);\n\t\n\t\n\t/*---- Private constant ----*/\n\t\n\t/* The set of all legal characters in alphanumeric mode, where\n\t * each character value maps to the index in the string. */\n\tprivate: static const char *ALPHANUMERIC_CHARSET;\n\t\n};\n\n\n\n/* \n * A QR Code symbol, which is a type of two-dimension barcode.\n * Invented by Denso Wave and described in the ISO/IEC 18004 standard.\n * Instances of this class represent an immutable square grid of dark and light cells.\n * The class provides static factory functions to create a QR Code from text or binary data.\n * The class covers the QR Code Model 2 specification, supporting all versions (sizes)\n * from 1 to 40, all 4 error correction levels, and 4 character encoding modes.\n * \n * Ways to create a QR Code object:\n * - High level: Take the payload data and call QrCode::encodeText() or QrCode::encodeBinary().\n * - Mid level: Custom-make the list of segments and call QrCode::encodeSegments().\n * - Low level: Custom-make the array of data codeword bytes (including\n *   segment headers and final padding, excluding error correction codewords),\n *   supply the appropriate version number, and call the QrCode() constructor.\n * (Note that all ways require supplying the desired error correction level.)\n */\nclass QrCode final {\n\t\n\t/*---- Public helper enumeration ----*/\n\t\n\t/* \n\t * The error correction level in a QR Code symbol.\n\t */\n\tpublic: enum class Ecc {\n\t\tLOW = 0 ,  // The QR Code can tolerate about  7% erroneous codewords\n\t\tMEDIUM  ,  // The QR Code can tolerate about 15% erroneous codewords\n\t\tQUARTILE,  // The QR Code can tolerate about 25% erroneous codewords\n\t\tHIGH    ,  // The QR Code can tolerate about 30% erroneous codewords\n\t};\n\t\n\t\n\t// Returns a value in the range 0 to 3 (unsigned 2-bit integer).\n\tprivate: static int getFormatBits(Ecc ecl);\n\t\n\t\n\t\n\t/*---- Static factory functions (high level) ----*/\n\t\n\t/* \n\t * Returns a QR Code representing the given Unicode text string at the given error correction level.\n\t * As a conservative upper bound, this function is guaranteed to succeed for strings that have 2953 or fewer\n\t * UTF-8 code units (not Unicode code points) if the low error correction level is used. The smallest possible\n\t * QR Code version is automatically chosen for the output. The ECC level of the result may be higher than\n\t * the ecl argument if it can be done without increasing the version.\n\t */\n\tpublic: static QrCode encodeText(const char *text, Ecc ecl);\n\t\n\t\n\t/* \n\t * Returns a QR Code representing the given binary data at the given error correction level.\n\t * This function always encodes using the binary segment mode, not any text mode. The maximum number of\n\t * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.\n\t * The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.\n\t */\n\tpublic: static QrCode encodeBinary(const std::vector<std::uint8_t> &data, Ecc ecl);\n\t\n\t\n\t/*---- Static factory functions (mid level) ----*/\n\t\n\t/* \n\t * Returns a QR Code representing the given segments with the given encoding parameters.\n\t * The smallest possible QR Code version within the given range is automatically\n\t * chosen for the output. Iff boostEcl is true, then the ECC level of the result\n\t * may be higher than the ecl argument if it can be done without increasing the\n\t * version. The mask number is either between 0 to 7 (inclusive) to force that\n\t * mask, or -1 to automatically choose an appropriate mask (which may be slow).\n\t * This function allows the user to create a custom sequence of segments that switches\n\t * between modes (such as alphanumeric and byte) to encode text in less space.\n\t * This is a mid-level API; the high-level API is encodeText() and encodeBinary().\n\t */\n\tpublic: static QrCode encodeSegments(const std::vector<QrSegment> &segs, Ecc ecl,\n\t\tint minVersion=1, int maxVersion=40, int mask=-1, bool boostEcl=true);  // All optional parameters\n\t\n\t\n\t\n\t/*---- Instance fields ----*/\n\t\n\t// Immutable scalar parameters:\n\t\n\t/* The version number of this QR Code, which is between 1 and 40 (inclusive).\n\t * This determines the size of this barcode. */\n\tprivate: int version;\n\t\n\t/* The width and height of this QR Code, measured in modules, between\n\t * 21 and 177 (inclusive). This is equal to version * 4 + 17. */\n\tprivate: int size;\n\t\n\t/* The error correction level used in this QR Code. */\n\tprivate: Ecc errorCorrectionLevel;\n\t\n\t/* The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive).\n\t * Even if a QR Code is created with automatic masking requested (mask = -1),\n\t * the resulting object still has a mask value between 0 and 7. */\n\tprivate: int mask;\n\t\n\t// Private grids of modules/pixels, with dimensions of size*size:\n\t\n\t// The modules of this QR Code (false = light, true = dark).\n\t// Immutable after constructor finishes. Accessed through getModule().\n\tprivate: std::vector<std::vector<bool> > modules;\n\t\n\t// Indicates function modules that are not subjected to masking. Discarded when constructor finishes.\n\tprivate: std::vector<std::vector<bool> > isFunction;\n\t\n\t\n\t\n\t/*---- Constructor (low level) ----*/\n\t\n\t/* \n\t * Creates a new QR Code with the given version number,\n\t * error correction level, data codeword bytes, and mask number.\n\t * This is a low-level API that most users should not use directly.\n\t * A mid-level API is the encodeSegments() function.\n\t */\n\tpublic: QrCode(int ver, Ecc ecl, const std::vector<std::uint8_t> &dataCodewords, int msk);\n\t\n\t\n\t\n\t/*---- Public instance methods ----*/\n\t\n\t/* \n\t * Returns this QR Code's version, in the range [1, 40].\n\t */\n\tpublic: int getVersion() const;\n\t\n\t\n\t/* \n\t * Returns this QR Code's size, in the range [21, 177].\n\t */\n\tpublic: int getSize() const;\n\t\n\t\n\t/* \n\t * Returns this QR Code's error correction level.\n\t */\n\tpublic: Ecc getErrorCorrectionLevel() const;\n\t\n\t\n\t/* \n\t * Returns this QR Code's mask, in the range [0, 7].\n\t */\n\tpublic: int getMask() const;\n\t\n\t\n\t/* \n\t * Returns the color of the module (pixel) at the given coordinates, which is false\n\t * for light or true for dark. The top left corner has the coordinates (x=0, y=0).\n\t * If the given coordinates are out of bounds, then false (light) is returned.\n\t */\n\tpublic: bool getModule(int x, int y) const;\n\t\n\t\n\t\n\t/*---- Private helper methods for constructor: Drawing function modules ----*/\n\t\n\t// Reads this object's version field, and draws and marks all function modules.\n\tprivate: void drawFunctionPatterns();\n\t\n\t\n\t// Draws two copies of the format bits (with its own error correction code)\n\t// based on the given mask and this object's error correction level field.\n\tprivate: void drawFormatBits(int msk);\n\t\n\t\n\t// Draws two copies of the version bits (with its own error correction code),\n\t// based on this object's version field, iff 7 <= version <= 40.\n\tprivate: void drawVersion();\n\t\n\t\n\t// Draws a 9*9 finder pattern including the border separator,\n\t// with the center module at (x, y). Modules can be out of bounds.\n\tprivate: void drawFinderPattern(int x, int y);\n\t\n\t\n\t// Draws a 5*5 alignment pattern, with the center module\n\t// at (x, y). All modules must be in bounds.\n\tprivate: void drawAlignmentPattern(int x, int y);\n\t\n\t\n\t// Sets the color of a module and marks it as a function module.\n\t// Only used by the constructor. Coordinates must be in bounds.\n\tprivate: void setFunctionModule(int x, int y, bool isDark);\n\t\n\t\n\t// Returns the color of the module at the given coordinates, which must be in range.\n\tprivate: bool module(int x, int y) const;\n\t\n\t\n\t/*---- Private helper methods for constructor: Codewords and masking ----*/\n\t\n\t// Returns a new byte string representing the given data with the appropriate error correction\n\t// codewords appended to it, based on this object's version and error correction level.\n\tprivate: std::vector<std::uint8_t> addEccAndInterleave(const std::vector<std::uint8_t> &data) const;\n\t\n\t\n\t// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire\n\t// data area of this QR Code. Function modules need to be marked off before this is called.\n\tprivate: void drawCodewords(const std::vector<std::uint8_t> &data);\n\t\n\t\n\t// XORs the codeword modules in this QR Code with the given mask pattern.\n\t// The function modules must be marked and the codeword bits must be drawn\n\t// before masking. Due to the arithmetic of XOR, calling applyMask() with\n\t// the same mask value a second time will undo the mask. A final well-formed\n\t// QR Code needs exactly one (not zero, two, etc.) mask applied.\n\tprivate: void applyMask(int msk);\n\t\n\t\n\t// Calculates and returns the penalty score based on state of this QR Code's current modules.\n\t// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.\n\tprivate: long getPenaltyScore() const;\n\t\n\t\n\t\n\t/*---- Private helper functions ----*/\n\t\n\t// Returns an ascending list of positions of alignment patterns for this version number.\n\t// Each position is in the range [0,177), and are used on both the x and y axes.\n\t// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes.\n\tprivate: std::vector<int> getAlignmentPatternPositions() const;\n\t\n\t\n\t// Returns the number of data bits that can be stored in a QR Code of the given version number, after\n\t// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.\n\t// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.\n\tprivate: static int getNumRawDataModules(int ver);\n\t\n\t\n\t// Returns the number of 8-bit data (i.e. not error correction) codewords contained in any\n\t// QR Code of the given version number and error correction level, with remainder bits discarded.\n\t// This stateless pure function could be implemented as a (40*4)-cell lookup table.\n\tprivate: static int getNumDataCodewords(int ver, Ecc ecl);\n\t\n\t\n\t// Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be\n\t// implemented as a lookup table over all possible parameter values, instead of as an algorithm.\n\tprivate: static std::vector<std::uint8_t> reedSolomonComputeDivisor(int degree);\n\t\n\t\n\t// Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials.\n\tprivate: static std::vector<std::uint8_t> reedSolomonComputeRemainder(const std::vector<std::uint8_t> &data, const std::vector<std::uint8_t> &divisor);\n\t\n\t\n\t// Returns the product of the two given field elements modulo GF(2^8/0x11D).\n\t// All inputs are valid. This could be implemented as a 256*256 lookup table.\n\tprivate: static std::uint8_t reedSolomonMultiply(std::uint8_t x, std::uint8_t y);\n\t\n\t\n\t// Can only be called immediately after a light run is added, and\n\t// returns either 0, 1, or 2. A helper function for getPenaltyScore().\n\tprivate: int finderPenaltyCountPatterns(const std::array<int,7> &runHistory) const;\n\t\n\t\n\t// Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore().\n\tprivate: int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array<int,7> &runHistory) const;\n\t\n\t\n\t// Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore().\n\tprivate: void finderPenaltyAddHistory(int currentRunLength, std::array<int,7> &runHistory) const;\n\t\n\t\n\t// Returns true iff the i'th bit of x is set to 1.\n\tprivate: static bool getBit(long x, int i);\n\t\n\t\n\t/*---- Constants and tables ----*/\n\t\n\t// The minimum version number supported in the QR Code Model 2 standard.\n\tpublic: static constexpr int MIN_VERSION =  1;\n\t\n\t// The maximum version number supported in the QR Code Model 2 standard.\n\tpublic: static constexpr int MAX_VERSION = 40;\n\t\n\t\n\t// For use in getPenaltyScore(), when evaluating which mask is best.\n\tprivate: static const int PENALTY_N1;\n\tprivate: static const int PENALTY_N2;\n\tprivate: static const int PENALTY_N3;\n\tprivate: static const int PENALTY_N4;\n\t\n\t\n\tprivate: static const std::int8_t ECC_CODEWORDS_PER_BLOCK[4][41];\n\tprivate: static const std::int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41];\n\t\n};\n\n\n\n/*---- Public exception class ----*/\n\n/* \n * Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include:\n * - Decrease the error correction level if it was greater than Ecc::LOW.\n * - If the encodeSegments() function was called with a maxVersion argument, then increase\n *   it if it was less than QrCode::MAX_VERSION. (This advice does not apply to the other\n *   factory functions because they search all versions up to QrCode::MAX_VERSION.)\n * - Split the text data into better or optimal segments in order to reduce the number of bits required.\n * - Change the text or binary data to be shorter.\n * - Change the text to fit the character set of a particular segment mode (e.g. alphanumeric).\n * - Propagate the error upward to the caller/user.\n */\nclass data_too_long : public std::length_error {\n\t\n\tpublic: explicit data_too_long(const std::string &msg);\n\t\n};\n\n\n\n/* \n * An appendable sequence of bits (0s and 1s). Mainly used by QrSegment.\n */\nclass BitBuffer final : public std::vector<bool> {\n\t\n\t/*---- Constructor ----*/\n\t\n\t// Creates an empty bit buffer (length 0).\n\tpublic: BitBuffer();\n\t\n\t\n\t\n\t/*---- Method ----*/\n\t\n\t// Appends the given number of low-order bits of the given value\n\t// to this buffer. Requires 0 <= len <= 31 and val < 2^len.\n\tpublic: void appendBits(std::uint32_t val, int len);\n\t\n};\n\n}\n"
  },
  {
    "path": "3rdparty/qscopeguard.h",
    "content": "/****************************************************************************\n**\n** Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Sérgio Martins <sergio.martins@kdab.com>\n** Contact: https://www.qt.io/licensing/\n**\n** This file is part of the QtCore module of the Qt Toolkit.\n**\n** $QT_BEGIN_LICENSE:LGPL$\n** Commercial License Usage\n** Licensees holding valid commercial Qt licenses may use this file in\n** accordance with the commercial license agreement provided with the\n** Software or, alternatively, in accordance with the terms contained in\n** a written agreement between you and The Qt Company. For licensing terms\n** and conditions see https://www.qt.io/terms-conditions. For further\n** information use the contact form at https://www.qt.io/contact-us.\n**\n** GNU Lesser General Public License Usage\n** Alternatively, this file may be used under the terms of the GNU Lesser\n** General Public License version 3 as published by the Free Software\n** Foundation and appearing in the file LICENSE.LGPL3 included in the\n** packaging of this file. Please review the following information to\n** ensure the GNU Lesser General Public License version 3 requirements\n** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.\n**\n** GNU General Public License Usage\n** Alternatively, this file may be used under the terms of the GNU\n** General Public License version 2.0 or (at your option) the GNU General\n** Public license version 3 or any later version approved by the KDE Free\n** Qt Foundation. The licenses are as published by the Free Software\n** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3\n** included in the packaging of this file. Please review the following\n** information to ensure the GNU General Public License requirements will\n** be met: https://www.gnu.org/licenses/gpl-2.0.html and\n** https://www.gnu.org/licenses/gpl-3.0.html.\n**\n** $QT_END_LICENSE$\n**\n****************************************************************************/\n\n#ifndef QSCOPEGUARD_H\n#define QSCOPEGUARD_H\n\n#include <QtCore/qglobal.h>\n\n\nQT_BEGIN_NAMESPACE\n\n\ntemplate <typename F> class QScopeGuard;\ntemplate <typename F> QScopeGuard<F> qScopeGuard(F f);\n\ntemplate <typename F>\nclass QScopeGuard\n{\npublic:\n    QScopeGuard(QScopeGuard &&other) Q_DECL_NOEXCEPT\n        : m_func(std::move(other.m_func))\n        , m_invoke(other.m_invoke)\n    {\n        other.dismiss();\n    }\n\n    ~QScopeGuard()\n    {\n        if (m_invoke)\n            m_func();\n    }\n\n    void dismiss() Q_DECL_NOEXCEPT\n    {\n        m_invoke = false;\n    }\n\nprivate:\n    explicit QScopeGuard(F f) Q_DECL_NOEXCEPT\n        : m_func(std::move(f))\n    {\n    }\n\n    Q_DISABLE_COPY(QScopeGuard)\n\n    F m_func;\n    bool m_invoke = true;\n    friend QScopeGuard qScopeGuard<F>(F);\n};\n\n\ntemplate <typename F>\nQScopeGuard<F> qScopeGuard(F f)\n{\n    return QScopeGuard<F>(std::move(f));\n}\n\nQT_END_NAMESPACE\n\n#endif // QSCOPEGUARD_H\n"
  },
  {
    "path": "3rdparty/qv2ray/v2/components/proxy/QvProxyConfigurator.cpp",
    "content": "#include \"QvProxyConfigurator.hpp\"\n\n#ifdef Q_OS_WIN\n//\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#include <windows.h>\n//\n#include <wininet.h>\n#include <ras.h>\n#include <raserror.h>\n#include <vector>\n#endif\n\n#include <QStandardPaths>\n#include <QProcess>\n\n#include \"3rdparty/fix_old_qt.h\"\n#include \"3rdparty/qv2ray/wrapper.hpp\"\n#include \"fmt/Preset.hpp\"\n#include \"main/NekoGui.hpp\"\n\n#define QV_MODULE_NAME \"SystemProxy\"\n\n#define QSTRN(num) QString::number(num)\n\nnamespace Qv2ray::components::proxy {\n\n    using ProcessArgument = QPair<QString, QStringList>;\n#ifdef Q_OS_MACOS\n    QStringList macOSgetNetworkServices() {\n        QProcess p;\n        p.setProgram(\"/usr/sbin/networksetup\");\n        p.setArguments(QStringList{\"-listallnetworkservices\"});\n        p.start();\n        p.waitForStarted();\n        p.waitForFinished();\n        LOG(p.errorString());\n        auto str = p.readAllStandardOutput();\n        auto lines = SplitLines(str);\n        QStringList result;\n\n        // Start from 1 since first line is unneeded.\n        for (auto i = 1; i < lines.count(); i++) {\n            // * means disabled.\n            if (!lines[i].contains(\"*\")) {\n                result << lines[i];\n            }\n        }\n\n        LOG(\"Found \" + QSTRN(result.size()) + \" network services: \" + result.join(\";\"));\n        return result;\n    }\n#endif\n#ifdef Q_OS_WIN\n#define NO_CONST(expr) const_cast<wchar_t *>(expr)\n    // static auto DEFAULT_CONNECTION_NAME =\n    // NO_CONST(L\"DefaultConnectionSettings\");\n    ///\n    /// INTERNAL FUNCTION\n    bool __QueryProxyOptions() {\n        INTERNET_PER_CONN_OPTION_LIST List;\n        INTERNET_PER_CONN_OPTION Option[5];\n        //\n        unsigned long nSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);\n        Option[0].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;\n        Option[1].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS;\n        Option[2].dwOption = INTERNET_PER_CONN_FLAGS;\n        Option[3].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;\n        Option[4].dwOption = INTERNET_PER_CONN_PROXY_SERVER;\n        //\n        List.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);\n        List.pszConnection = nullptr; // NO_CONST(DEFAULT_CONNECTION_NAME);\n        List.dwOptionCount = 5;\n        List.dwOptionError = 0;\n        List.pOptions = Option;\n\n        if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) {\n            LOG(\"InternetQueryOption failed, GLE=\" + QSTRN(GetLastError()));\n        }\n\n        LOG(\"System default proxy info:\");\n\n        if (Option[0].Value.pszValue != nullptr) {\n            LOG(QString::fromWCharArray(Option[0].Value.pszValue));\n        }\n\n        if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_PROXY_URL) == PROXY_TYPE_AUTO_PROXY_URL) {\n            LOG(\"PROXY_TYPE_AUTO_PROXY_URL\");\n        }\n\n        if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_DETECT) == PROXY_TYPE_AUTO_DETECT) {\n            LOG(\"PROXY_TYPE_AUTO_DETECT\");\n        }\n\n        if ((Option[2].Value.dwValue & PROXY_TYPE_DIRECT) == PROXY_TYPE_DIRECT) {\n            LOG(\"PROXY_TYPE_DIRECT\");\n        }\n\n        if ((Option[2].Value.dwValue & PROXY_TYPE_PROXY) == PROXY_TYPE_PROXY) {\n            LOG(\"PROXY_TYPE_PROXY\");\n        }\n\n        if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) {\n            LOG(\"InternetQueryOption failed,GLE=\" + QSTRN(GetLastError()));\n        }\n\n        if (Option[4].Value.pszValue != nullptr) {\n            LOG(QString::fromStdWString(Option[4].Value.pszValue));\n        }\n\n        INTERNET_VERSION_INFO Version;\n        nSize = sizeof(INTERNET_VERSION_INFO);\n        InternetQueryOption(nullptr, INTERNET_OPTION_VERSION, &Version, &nSize);\n\n        if (Option[0].Value.pszValue != nullptr) {\n            GlobalFree(Option[0].Value.pszValue);\n        }\n\n        if (Option[3].Value.pszValue != nullptr) {\n            GlobalFree(Option[3].Value.pszValue);\n        }\n\n        if (Option[4].Value.pszValue != nullptr) {\n            GlobalFree(Option[4].Value.pszValue);\n        }\n\n        return false;\n    }\n    bool __SetProxyOptions(LPWSTR proxy_full_addr, bool isPAC) {\n        INTERNET_PER_CONN_OPTION_LIST list;\n        DWORD dwBufSize = sizeof(list);\n        // Fill the list structure.\n        list.dwSize = sizeof(list);\n        // NULL == LAN, otherwise connectoid name.\n        list.pszConnection = nullptr;\n\n        if (nullptr == proxy_full_addr) {\n            LOG(\"Clearing system proxy\");\n            //\n            list.dwOptionCount = 1;\n            list.pOptions = new INTERNET_PER_CONN_OPTION[1];\n\n            // Ensure that the memory was allocated.\n            if (nullptr == list.pOptions) {\n                // Return if the memory wasn't allocated.\n                return false;\n            }\n\n            // Set flags.\n            list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;\n            list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT;\n        } else if (isPAC) {\n            LOG(\"Setting system proxy for PAC\");\n            //\n            list.dwOptionCount = 2;\n            list.pOptions = new INTERNET_PER_CONN_OPTION[2];\n\n            if (nullptr == list.pOptions) {\n                return false;\n            }\n\n            // Set flags.\n            list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;\n            list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_AUTO_PROXY_URL;\n            // Set proxy name.\n            list.pOptions[1].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;\n            list.pOptions[1].Value.pszValue = proxy_full_addr;\n        } else {\n            LOG(\"Setting system proxy for Global Proxy\");\n            //\n            list.dwOptionCount = 2;\n            list.pOptions = new INTERNET_PER_CONN_OPTION[2];\n\n            if (nullptr == list.pOptions) {\n                return false;\n            }\n\n            // Set flags.\n            list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;\n            list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY;\n            // Set proxy name.\n            list.pOptions[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER;\n            list.pOptions[1].Value.pszValue = proxy_full_addr;\n            // Set proxy override.\n            // list.pOptions[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;\n            // auto localhost = L\"localhost\";\n            // list.pOptions[2].Value.pszValue = NO_CONST(localhost);\n        }\n\n        // Set proxy for LAN.\n        if (!InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize)) {\n            LOG(\"InternetSetOption failed for LAN, GLE=\" + QSTRN(GetLastError()));\n        }\n\n        RASENTRYNAME entry;\n        entry.dwSize = sizeof(entry);\n        std::vector<RASENTRYNAME> entries;\n        DWORD size = sizeof(entry), count;\n        LPRASENTRYNAME entryAddr = &entry;\n        auto ret = RasEnumEntries(nullptr, nullptr, entryAddr, &size, &count);\n        if (ERROR_BUFFER_TOO_SMALL == ret) {\n            entries.resize(count);\n            entries[0].dwSize = sizeof(RASENTRYNAME);\n            entryAddr = entries.data();\n            ret = RasEnumEntries(nullptr, nullptr, entryAddr, &size, &count);\n        }\n        if (ERROR_SUCCESS != ret) {\n            LOG(\"Failed to list entry names\");\n            return false;\n        }\n\n        // Set proxy for each connectoid.\n        for (DWORD i = 0; i < count; ++i) {\n            list.pszConnection = entryAddr[i].szEntryName;\n            if (!InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize)) {\n                LOG(\"InternetSetOption failed for connectoid \" + QString::fromWCharArray(list.pszConnection) + \", GLE=\" + QSTRN(GetLastError()));\n            }\n        }\n\n        delete[] list.pOptions;\n        InternetSetOption(nullptr, INTERNET_OPTION_SETTINGS_CHANGED, nullptr, 0);\n        InternetSetOption(nullptr, INTERNET_OPTION_REFRESH, nullptr, 0);\n        return true;\n    }\n#endif\n\n    void SetSystemProxy(int httpPort, int socksPort) {\n        const QString &address = \"127.0.0.1\";\n        bool hasHTTP = (httpPort > 0 && httpPort < 65536);\n        bool hasSOCKS = (socksPort > 0 && socksPort < 65536);\n\n#ifdef Q_OS_WIN\n        if (!hasHTTP) {\n            LOG(\"Nothing?\");\n            return;\n        } else {\n            LOG(\"Qv2ray will set system proxy to use HTTP\");\n        }\n#else\n        if (!hasHTTP && !hasSOCKS) {\n            LOG(\"Nothing?\");\n            return;\n        }\n\n        if (hasHTTP) {\n            LOG(\"Qv2ray will set system proxy to use HTTP\");\n        }\n\n        if (hasSOCKS) {\n            LOG(\"Qv2ray will set system proxy to use SOCKS\");\n        }\n#endif\n\n#ifdef Q_OS_WIN\n        QString str = NekoGui::dataStore->system_proxy_format;\n        if (str.isEmpty()) str = Preset::Windows::system_proxy_format[0];\n        str = str.replace(\"{ip}\", address)\n                  .replace(\"{http_port}\", Int2String(httpPort))\n                  .replace(\"{socks_port}\", Int2String(socksPort));\n        //\n        LOG(\"Windows proxy string: \" + str);\n        auto proxyStrW = new WCHAR[str.length() + 1];\n        wcscpy(proxyStrW, str.toStdWString().c_str());\n        //\n        __QueryProxyOptions();\n\n        if (!__SetProxyOptions(proxyStrW, false)) {\n            LOG(\"Failed to set proxy.\");\n        }\n\n        __QueryProxyOptions();\n#elif defined(Q_OS_LINUX)\n        QList<ProcessArgument> actions;\n        actions << ProcessArgument{\"gsettings\", {\"set\", \"org.gnome.system.proxy\", \"mode\", \"manual\"}};\n        //\n        bool isKDE = qEnvironmentVariable(\"XDG_SESSION_DESKTOP\") == \"KDE\" ||\n                     qEnvironmentVariable(\"XDG_SESSION_DESKTOP\") == \"plasma\";\n        const auto configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);\n\n        //\n        // Configure HTTP Proxies for HTTP, FTP and HTTPS\n        if (hasHTTP) {\n            // iterate over protocols...\n            for (const auto &protocol: QStringList{\"http\", \"ftp\", \"https\"}) {\n                // for GNOME:\n                {\n                    actions << ProcessArgument{\"gsettings\",\n                                               {\"set\", \"org.gnome.system.proxy.\" + protocol, \"host\", address}};\n                    actions << ProcessArgument{\"gsettings\",\n                                               {\"set\", \"org.gnome.system.proxy.\" + protocol, \"port\", QSTRN(httpPort)}};\n                }\n\n                // for KDE:\n                if (isKDE) {\n                    actions << ProcessArgument{\"kwriteconfig5\",\n                                               {\"--file\", configPath + \"/kioslaverc\", //\n                                                \"--group\", \"Proxy Settings\",          //\n                                                \"--key\", protocol + \"Proxy\",          //\n                                                \"http://\" + address + \" \" + QSTRN(httpPort)}};\n                }\n            }\n        }\n\n        // Configure SOCKS5 Proxies\n        if (hasSOCKS) {\n            // for GNOME:\n            {\n                actions << ProcessArgument{\"gsettings\", {\"set\", \"org.gnome.system.proxy.socks\", \"host\", address}};\n                actions << ProcessArgument{\"gsettings\",\n                                           {\"set\", \"org.gnome.system.proxy.socks\", \"port\", QSTRN(socksPort)}};\n\n                // for KDE:\n                if (isKDE) {\n                    actions << ProcessArgument{\"kwriteconfig5\",\n                                               {\"--file\", configPath + \"/kioslaverc\", //\n                                                \"--group\", \"Proxy Settings\",          //\n                                                \"--key\", \"socksProxy\",                //\n                                                \"socks://\" + address + \" \" + QSTRN(socksPort)}};\n                }\n            }\n        }\n        // Setting Proxy Mode to Manual\n        {\n            // for GNOME:\n            {\n                actions << ProcessArgument{\"gsettings\", {\"set\", \"org.gnome.system.proxy\", \"mode\", \"manual\"}};\n            }\n\n            // for KDE:\n            if (isKDE) {\n                actions << ProcessArgument{\"kwriteconfig5\",\n                                           {\"--file\", configPath + \"/kioslaverc\", //\n                                            \"--group\", \"Proxy Settings\",          //\n                                            \"--key\", \"ProxyType\", \"1\"}};\n            }\n        }\n\n        // Notify kioslaves to reload system proxy configuration.\n        if (isKDE) {\n            actions << ProcessArgument{\"dbus-send\",\n                                       {\"--type=signal\", \"/KIO/Scheduler\",                 //\n                                        \"org.kde.KIO.Scheduler.reparseSlaveConfiguration\", //\n                                        \"string:''\"}};\n        }\n        // Execute them all!\n        //\n        // note: do not use std::all_of / any_of / none_of,\n        // because those are short-circuit and cannot guarantee atomicity.\n        QList<bool> results;\n        for (const auto &action: actions) {\n            // execute and get the code\n            const auto returnCode = QProcess::execute(action.first, action.second);\n            // print out the commands and result codes\n            DEBUG(QStringLiteral(\"[%1] Program: %2, Args: %3\").arg(returnCode).arg(action.first).arg(action.second.join(\";\")));\n            // give the code back\n            results << (returnCode == QProcess::NormalExit);\n        }\n\n        if (results.count(true) != actions.size()) {\n            LOG(\"Something wrong when setting proxies.\");\n        }\n#else\n\n        for (const auto &service: macOSgetNetworkServices()) {\n            LOG(\"Setting proxy for interface: \" + service);\n            if (hasHTTP) {\n                QProcess::execute(\"/usr/sbin/networksetup\", {\"-setwebproxystate\", service, \"on\"});\n                QProcess::execute(\"/usr/sbin/networksetup\", {\"-setsecurewebproxystate\", service, \"on\"});\n                QProcess::execute(\"/usr/sbin/networksetup\", {\"-setwebproxy\", service, address, QSTRN(httpPort)});\n                QProcess::execute(\"/usr/sbin/networksetup\", {\"-setsecurewebproxy\", service, address, QSTRN(httpPort)});\n            }\n\n            if (hasSOCKS) {\n                QProcess::execute(\"/usr/sbin/networksetup\", {\"-setsocksfirewallproxystate\", service, \"on\"});\n                QProcess::execute(\"/usr/sbin/networksetup\", {\"-setsocksfirewallproxy\", service, address, QSTRN(socksPort)});\n            }\n        }\n\n#endif\n    }\n\n    void ClearSystemProxy() {\n        LOG(\"Clearing System Proxy\");\n\n#ifdef Q_OS_WIN\n        if (!__SetProxyOptions(nullptr, false)) {\n            LOG(\"Failed to clear proxy.\");\n        }\n#elif defined(Q_OS_LINUX)\n        QList<ProcessArgument> actions;\n        const bool isKDE = qEnvironmentVariable(\"XDG_SESSION_DESKTOP\") == \"KDE\" ||\n                           qEnvironmentVariable(\"XDG_SESSION_DESKTOP\") == \"plasma\";\n        const auto configRoot = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);\n\n        // Setting System Proxy Mode to: None\n        {\n            // for GNOME:\n            {\n                actions << ProcessArgument{\"gsettings\", {\"set\", \"org.gnome.system.proxy\", \"mode\", \"none\"}};\n            }\n\n            // for KDE:\n            if (isKDE) {\n                actions << ProcessArgument{\"kwriteconfig5\",\n                                           {\"--file\", configRoot + \"/kioslaverc\", //\n                                            \"--group\", \"Proxy Settings\",          //\n                                            \"--key\", \"ProxyType\", \"0\"}};\n            }\n        }\n\n        // Notify kioslaves to reload system proxy configuration.\n        if (isKDE) {\n            actions << ProcessArgument{\"dbus-send\",\n                                       {\"--type=signal\", \"/KIO/Scheduler\",                 //\n                                        \"org.kde.KIO.Scheduler.reparseSlaveConfiguration\", //\n                                        \"string:''\"}};\n        }\n\n        // Execute the Actions\n        for (const auto &action: actions) {\n            // execute and get the code\n            const auto returnCode = QProcess::execute(action.first, action.second);\n            // print out the commands and result codes\n            DEBUG(QStringLiteral(\"[%1] Program: %2, Args: %3\").arg(returnCode).arg(action.first).arg(action.second.join(\";\")));\n        }\n\n#else\n        for (const auto &service: macOSgetNetworkServices()) {\n            LOG(\"Clearing proxy for interface: \" + service);\n            QProcess::execute(\"/usr/sbin/networksetup\", {\"-setautoproxystate\", service, \"off\"});\n            QProcess::execute(\"/usr/sbin/networksetup\", {\"-setwebproxystate\", service, \"off\"});\n            QProcess::execute(\"/usr/sbin/networksetup\", {\"-setsecurewebproxystate\", service, \"off\"});\n            QProcess::execute(\"/usr/sbin/networksetup\", {\"-setsocksfirewallproxystate\", service, \"off\"});\n        }\n\n#endif\n    }\n} // namespace Qv2ray::components::proxy\n"
  },
  {
    "path": "3rdparty/qv2ray/v2/components/proxy/QvProxyConfigurator.hpp",
    "content": "#pragma once\n#include <QHostAddress>\n#include <QObject>\n#include <QString>\n//\nnamespace Qv2ray::components::proxy {\n    void ClearSystemProxy();\n    void SetSystemProxy(int http_port, int socks_port);\n} // namespace Qv2ray::components::proxy\n\nusing namespace Qv2ray::components;\nusing namespace Qv2ray::components::proxy;\n"
  },
  {
    "path": "3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.cpp",
    "content": "/****************************************************************************\n**\n** Copyright (C) 2016 The Qt Company Ltd.\n** Contact: https://www.qt.io/licensing/\n**\n** This file is part of the examples of the Qt Toolkit.\n**\n** $QT_BEGIN_LICENSE:BSD$\n** Commercial License Usage\n** Licensees holding valid commercial Qt licenses may use this file in\n** accordance with the commercial license agreement provided with the\n** Software or, alternatively, in accordance with the terms contained in\n** a written agreement between you and The Qt Company. For licensing terms\n** and conditions see https://www.qt.io/terms-conditions. For further\n** information use the contact form at https://www.qt.io/contact-us.\n**\n** BSD License Usage\n** Alternatively, you may use this file under the terms of the BSD license\n** as follows:\n**\n** \"Redistribution and use in source and binary forms, with or without\n** modification, are permitted provided that the following conditions are\n** met:\n**   * Redistributions of source code must retain the above copyright\n**     notice, this list of conditions and the following disclaimer.\n**   * Redistributions in binary form must reproduce the above copyright\n**     notice, this list of conditions and the following disclaimer in\n**     the documentation and/or other materials provided with the\n**     distribution.\n**   * Neither the name of The Qt Company Ltd nor the names of its\n**     contributors may be used to endorse or promote products derived\n**     from this software without specific prior written permission.\n**\n**\n** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n** \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\"\n**\n** $QT_END_LICENSE$\n**\n****************************************************************************/\n\n#include \"QvAutoCompleteTextEdit.hpp\"\n\n#include <QAbstractItemModel>\n#include <QAbstractItemView>\n#include <QApplication>\n#include <QCompleter>\n#include <QKeyEvent>\n#include <QModelIndex>\n#include <QScrollBar>\n#include <QStringListModel>\n#include <QToolTip>\n#include <QtDebug>\n\nnamespace Qv2ray::ui::widgets {\n    AutoCompleteTextEdit::AutoCompleteTextEdit(const QString &prefix, const QStringList &sourceStrings, QWidget *parent) : QPlainTextEdit(parent) {\n        this->prefix = prefix;\n        this->setLineWrapMode(QPlainTextEdit::NoWrap);\n        c = new QCompleter(this);\n        c->setModel(new QStringListModel(sourceStrings, c));\n        c->setWidget(this);\n        c->setCompletionMode(QCompleter::PopupCompletion);\n        c->setCaseSensitivity(Qt::CaseInsensitive);\n        QObject::connect(c, static_cast<void (QCompleter::*)(const QString &)>(&QCompleter::activated), this, &AutoCompleteTextEdit::insertCompletion);\n    }\n\n    AutoCompleteTextEdit::~AutoCompleteTextEdit() {\n    }\n\n    void AutoCompleteTextEdit::insertCompletion(const QString &completion) {\n        QTextCursor tc = textCursor();\n        int extra = completion.length() - c->completionPrefix().length();\n        tc.movePosition(QTextCursor::Left);\n        tc.movePosition(QTextCursor::EndOfWord);\n        tc.insertText(completion.right(extra).toLower());\n        setTextCursor(tc);\n    }\n\n    QString AutoCompleteTextEdit::lineUnderCursor() const {\n        QTextCursor tc = textCursor();\n        tc.select(QTextCursor::LineUnderCursor);\n        return tc.selectedText();\n    }\n\n    QString AutoCompleteTextEdit::wordUnderCursor() const {\n        QTextCursor tc = textCursor();\n        tc.select(QTextCursor::WordUnderCursor);\n        return tc.selectedText();\n    }\n\n    void AutoCompleteTextEdit::focusInEvent(QFocusEvent *e) {\n        if (c)\n            c->setWidget(this);\n\n        QPlainTextEdit::focusInEvent(e);\n    }\n\n    void AutoCompleteTextEdit::keyPressEvent(QKeyEvent *e) {\n        const bool hasCtrlOrShiftModifier = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::ShiftModifier);\n        const bool hasOtherModifiers = (e->modifiers() != Qt::NoModifier) && !hasCtrlOrShiftModifier; // has other modifiers\n        //\n        const bool isSpace = (e->modifiers().testFlag(Qt::ShiftModifier) || e->modifiers().testFlag(Qt::NoModifier)) //\n                             && e->key() == Qt::Key_Space;\n        const bool isTab = (e->modifiers().testFlag(Qt::NoModifier) && e->key() == Qt::Key_Tab);\n        const bool isOtherSpace = e->text() == \"　\";\n        //\n        if (isSpace || isTab || isOtherSpace) {\n            QToolTip::showText(this->mapToGlobal(QPoint(0, 0)), tr(\"You can not input space characters here.\"), this, QRect{}, 2000);\n            return;\n        }\n        //\n        if (c && c->popup()->isVisible()) {\n            // The following keys are forwarded by the completer to the widget\n            switch (e->key()) {\n                case Qt::Key_Enter:\n                case Qt::Key_Return:\n                case Qt::Key_Escape:\n                case Qt::Key_Tab:\n                case Qt::Key_Backtab:\n                    e->ignore();\n                    return; // let the completer do default behavior\n\n                default:\n                    break;\n            }\n        }\n\n        QPlainTextEdit::keyPressEvent(e);\n\n        if (!c || (hasCtrlOrShiftModifier && e->text().isEmpty()))\n            return;\n\n        // if we have other modifiers, or the text is empty, or the line does not start with our prefix.\n        if (hasOtherModifiers || e->text().isEmpty() || !lineUnderCursor().startsWith(prefix)) {\n            c->popup()->hide();\n            return;\n        }\n\n        if (auto word = wordUnderCursor(); word != c->completionPrefix()) {\n            c->setCompletionPrefix(word);\n            c->popup()->setCurrentIndex(c->completionModel()->index(0, 0));\n        }\n\n        QRect cr = cursorRect();\n        cr.setWidth(c->popup()->sizeHintForColumn(0) + c->popup()->verticalScrollBar()->sizeHint().width());\n        c->complete(cr); // popup it up!\n    }\n} // namespace Qv2ray::ui::widgets\n"
  },
  {
    "path": "3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.hpp",
    "content": "/****************************************************************************\n**\n** Copyright (C) 2016 The Qt Company Ltd.\n** Contact: https://www.qt.io/licensing/\n**\n** This file is part of the examples of the Qt Toolkit.\n**\n** $QT_BEGIN_LICENSE:BSD$\n** Commercial License Usage\n** Licensees holding valid commercial Qt licenses may use this file in\n** accordance with the commercial license agreement provided with the\n** Software or, alternatively, in accordance with the terms contained in\n** a written agreement between you and The Qt Company. For licensing terms\n** and conditions see https://www.qt.io/terms-conditions. For further\n** information use the contact form at https://www.qt.io/contact-us.\n**\n** BSD License Usage\n** Alternatively, you may use this file under the terms of the BSD license\n** as follows:\n**\n** \"Redistribution and use in source and binary forms, with or without\n** modification, are permitted provided that the following conditions are\n** met:\n**   * Redistributions of source code must retain the above copyright\n**     notice, this list of conditions and the following disclaimer.\n**   * Redistributions in binary form must reproduce the above copyright\n**     notice, this list of conditions and the following disclaimer in\n**     the documentation and/or other materials provided with the\n**     distribution.\n**   * Neither the name of The Qt Company Ltd nor the names of its\n**     contributors may be used to endorse or promote products derived\n**     from this software without specific prior written permission.\n**\n**\n** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n** \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\"\n**\n** $QT_END_LICENSE$\n**\n****************************************************************************/\n\n#pragma once\n#include <QAbstractItemModel>\n#include <QPlainTextEdit>\nQT_BEGIN_NAMESPACE\nclass QCompleter;\nQT_END_NAMESPACE\n\nnamespace Qv2ray {\n    namespace ui {\n        namespace widgets {\n            class AutoCompleteTextEdit : public QPlainTextEdit {\n                Q_OBJECT\n\n            public:\n                AutoCompleteTextEdit(const QString &prefix, const QStringList &sourceStrings, QWidget *parent = nullptr);\n                ~AutoCompleteTextEdit();\n\n            protected:\n                void keyPressEvent(QKeyEvent *e) override;\n                void focusInEvent(QFocusEvent *e) override;\n\n            private slots:\n                void insertCompletion(const QString &completion);\n\n            private:\n                QString lineUnderCursor() const;\n                QString wordUnderCursor() const;\n\n                QString prefix;\n                QCompleter *c = nullptr;\n            };\n        } // namespace widgets\n    }     // namespace ui\n} // namespace Qv2ray\nusing namespace Qv2ray::ui::widgets;\n"
  },
  {
    "path": "3rdparty/qv2ray/v2/ui/widgets/common/QJsonModel.cpp",
    "content": "/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2011 SCHUTZ Sacha\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n#include \"QJsonModel.hpp\"\n\n#include <QDebug>\n#include <QFile>\n\nQJsonTreeItem::QJsonTreeItem(QJsonTreeItem *parent) {\n    mParent = parent;\n}\n\nQJsonTreeItem::~QJsonTreeItem() {\n    qDeleteAll(mChilds);\n}\n\nvoid QJsonTreeItem::appendChild(QJsonTreeItem *item) {\n    mChilds.append(item);\n}\n\nQJsonTreeItem *QJsonTreeItem::child(int row) {\n    return mChilds.value(row);\n}\n\nQJsonTreeItem *QJsonTreeItem::parent() {\n    return mParent;\n}\n\nint QJsonTreeItem::childCount() const {\n    return mChilds.count();\n}\n\nint QJsonTreeItem::row() const {\n    if (mParent)\n        return mParent->mChilds.indexOf(const_cast<QJsonTreeItem *>(this));\n\n    return 0;\n}\n\nvoid QJsonTreeItem::setKey(const QString &key) {\n    mKey = key;\n}\n\nvoid QJsonTreeItem::setValue(const QString &value) {\n    mValue = value;\n}\n\nvoid QJsonTreeItem::setType(const QJsonValue::Type &type) {\n    mType = type;\n}\n\nQString QJsonTreeItem::key() const {\n    return mKey;\n}\n\nQString QJsonTreeItem::value() const {\n    return mValue;\n}\n\nQJsonValue::Type QJsonTreeItem::type() const {\n    return mType;\n}\n\nQJsonTreeItem *QJsonTreeItem::load(const QJsonValue &value, QJsonTreeItem *parent) {\n    QJsonTreeItem *rootItem = new QJsonTreeItem(parent);\n    rootItem->setKey(\"root\");\n\n    if (value.isObject()) {\n        // Get all QJsonValue childs\n        for (QString key: value.toObject().keys()) {\n            QJsonValue v = value.toObject().value(key);\n            QJsonTreeItem *child = load(v, rootItem);\n            child->setKey(key);\n            child->setType(v.type());\n            rootItem->appendChild(child);\n        }\n    } else if (value.isArray()) {\n        // Get all QJsonValue childs\n        int index = 0;\n\n        for (QJsonValue v: value.toArray()) {\n            QJsonTreeItem *child = load(v, rootItem);\n            child->setKey(QString::number(index));\n            child->setType(v.type());\n            rootItem->appendChild(child);\n            ++index;\n        }\n    } else {\n        rootItem->setValue(value.toVariant().toString());\n        rootItem->setType(value.type());\n    }\n\n    return rootItem;\n}\n\n//=========================================================================\n\nQJsonModel::QJsonModel(QObject *parent) : QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} {\n    mHeaders.append(\"key\");\n    mHeaders.append(\"value\");\n}\n\nQJsonModel::QJsonModel(const QString &fileName, QObject *parent) : QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} {\n    mHeaders.append(\"key\");\n    mHeaders.append(\"value\");\n    load(fileName);\n}\n\nQJsonModel::QJsonModel(QIODevice *device, QObject *parent) : QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} {\n    mHeaders.append(\"key\");\n    mHeaders.append(\"value\");\n    load(device);\n}\n\nQJsonModel::QJsonModel(const QByteArray &json, QObject *parent) : QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} {\n    mHeaders.append(\"key\");\n    mHeaders.append(\"value\");\n    loadJson(json);\n}\n\nQJsonModel::~QJsonModel() {\n    delete mRootItem;\n}\n\nbool QJsonModel::load(const QString &fileName) {\n    QFile file(fileName);\n    bool success = false;\n\n    if (file.open(QIODevice::ReadOnly)) {\n        success = load(&file);\n        file.close();\n    } else\n        success = false;\n\n    return success;\n}\n\nbool QJsonModel::load(QIODevice *device) {\n    return loadJson(device->readAll());\n}\n\nbool QJsonModel::loadJson(const QByteArray &json) {\n    auto const &jdoc = QJsonDocument::fromJson(json);\n\n    if (!jdoc.isNull()) {\n        beginResetModel();\n        delete mRootItem;\n\n        if (jdoc.isArray()) {\n            mRootItem = QJsonTreeItem::load(QJsonValue(jdoc.array()));\n            mRootItem->setType(QJsonValue::Array);\n        } else {\n            mRootItem = QJsonTreeItem::load(QJsonValue(jdoc.object()));\n            mRootItem->setType(QJsonValue::Object);\n        }\n\n        endResetModel();\n        return true;\n    }\n\n    qDebug() << Q_FUNC_INFO << \"cannot load json\";\n    return false;\n}\n\nQVariant QJsonModel::data(const QModelIndex &index, int role) const {\n    if (!index.isValid())\n        return QVariant();\n\n    QJsonTreeItem *item = static_cast<QJsonTreeItem *>(index.internalPointer());\n\n    if (role == Qt::DisplayRole) {\n        if (index.column() == 0)\n            return QStringLiteral(\"%1\").arg(item->key());\n\n        if (index.column() == 1)\n            return QStringLiteral(\"%1\").arg(item->value());\n    } else if (Qt::EditRole == role) {\n        if (index.column() == 1) {\n            return QStringLiteral(\"%1\").arg(item->value());\n        }\n    }\n\n    return QVariant();\n}\n\nbool QJsonModel::setData(const QModelIndex &index, const QVariant &value, int role) {\n    int col = index.column();\n\n    if (Qt::EditRole == role) {\n        if (col == 1) {\n            QJsonTreeItem *item = static_cast<QJsonTreeItem *>(index.internalPointer());\n            item->setValue(value.toString());\n            emit dataChanged(index, index, {Qt::EditRole});\n            return true;\n        }\n    }\n\n    return false;\n}\n\nQVariant QJsonModel::headerData(int section, Qt::Orientation orientation, int role) const {\n    if (role != Qt::DisplayRole)\n        return QVariant();\n\n    if (orientation == Qt::Horizontal) {\n        return mHeaders.value(section);\n    } else\n        return QVariant();\n}\n\nQModelIndex QJsonModel::index(int row, int column, const QModelIndex &parent) const {\n    if (!hasIndex(row, column, parent))\n        return QModelIndex();\n\n    QJsonTreeItem *parentItem;\n\n    if (!parent.isValid())\n        parentItem = mRootItem;\n    else\n        parentItem = static_cast<QJsonTreeItem *>(parent.internalPointer());\n\n    QJsonTreeItem *childItem = parentItem->child(row);\n\n    if (childItem)\n        return createIndex(row, column, childItem);\n    else\n        return QModelIndex();\n}\n\nQModelIndex QJsonModel::parent(const QModelIndex &index) const {\n    if (!index.isValid())\n        return QModelIndex();\n\n    QJsonTreeItem *childItem = static_cast<QJsonTreeItem *>(index.internalPointer());\n    QJsonTreeItem *parentItem = childItem->parent();\n\n    if (parentItem == mRootItem)\n        return QModelIndex();\n\n    return createIndex(parentItem->row(), 0, parentItem);\n}\n\nint QJsonModel::rowCount(const QModelIndex &parent) const {\n    QJsonTreeItem *parentItem;\n\n    if (parent.column() > 0)\n        return 0;\n\n    if (!parent.isValid())\n        parentItem = mRootItem;\n    else\n        parentItem = static_cast<QJsonTreeItem *>(parent.internalPointer());\n\n    return parentItem->childCount();\n}\n\nint QJsonModel::columnCount(const QModelIndex &parent) const {\n    Q_UNUSED(parent)\n    return 2;\n}\n\nQt::ItemFlags QJsonModel::flags(const QModelIndex &index) const {\n    int col = index.column();\n    auto item = static_cast<QJsonTreeItem *>(index.internalPointer());\n    auto isArray = QJsonValue::Array == item->type();\n    auto isObject = QJsonValue::Object == item->type();\n\n    if ((col == 1) && !(isArray || isObject)) {\n        return Qt::ItemIsEditable | QAbstractItemModel::flags(index);\n    } else {\n        return QAbstractItemModel::flags(index);\n    }\n}\n\nQJsonDocument QJsonModel::json() const {\n    auto v = genJson(mRootItem);\n    QJsonDocument doc;\n\n    if (v.isObject()) {\n        doc = QJsonDocument(v.toObject());\n    } else {\n        doc = QJsonDocument(v.toArray());\n    }\n\n    return doc;\n}\n\nQJsonValue QJsonModel::genJson(QJsonTreeItem *item) const {\n    auto type = item->type();\n    int nchild = item->childCount();\n\n    if (QJsonValue::Object == type) {\n        QJsonObject jo;\n\n        for (int i = 0; i < nchild; ++i) {\n            auto ch = item->child(i);\n            auto key = ch->key();\n            jo.insert(key, genJson(ch));\n        }\n\n        return jo;\n    } else if (QJsonValue::Array == type) {\n        QJsonArray arr;\n\n        for (int i = 0; i < nchild; ++i) {\n            auto ch = item->child(i);\n            arr.append(genJson(ch));\n        }\n\n        return arr;\n    } else {\n        QJsonValue va(item->value());\n        return va;\n    }\n}\n"
  },
  {
    "path": "3rdparty/qv2ray/v2/ui/widgets/common/QJsonModel.hpp",
    "content": "/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2011 SCHUTZ Sacha\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n#pragma once\n\n#include <QAbstractItemModel>\n#include <QByteArray>\n#include <QIODevice>\n#include <QJsonArray>\n#include <QJsonDocument>\n#include <QJsonObject>\n#include <QJsonValue>\nclass QJsonModel;\nclass QJsonItem;\n\nclass QJsonTreeItem {\npublic:\n    QJsonTreeItem(QJsonTreeItem *parent = nullptr);\n    ~QJsonTreeItem();\n    void appendChild(QJsonTreeItem *item);\n    QJsonTreeItem *child(int row);\n    QJsonTreeItem *parent();\n    int childCount() const;\n    int row() const;\n    void setKey(const QString &key);\n    void setValue(const QString &value);\n    void setType(const QJsonValue::Type &type);\n    QString key() const;\n    QString value() const;\n    QJsonValue::Type type() const;\n\n    static QJsonTreeItem *load(const QJsonValue &value, QJsonTreeItem *parent = 0);\n\nprotected:\nprivate:\n    QString mKey;\n    QString mValue;\n    QJsonValue::Type mType;\n    QList<QJsonTreeItem *> mChilds;\n    QJsonTreeItem *mParent;\n};\n\n//---------------------------------------------------\n\nclass QJsonModel : public QAbstractItemModel {\n    Q_OBJECT\npublic:\n    explicit QJsonModel(QObject *parent = nullptr);\n    QJsonModel(const QString &fileName, QObject *parent = nullptr);\n    QJsonModel(QIODevice *device, QObject *parent = nullptr);\n    QJsonModel(const QByteArray &json, QObject *parent = nullptr);\n    ~QJsonModel();\n    bool load(const QString &fileName);\n    bool load(QIODevice *device);\n    bool loadJson(const QByteArray &json);\n    QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;\n    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE;\n    QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE;\n    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;\n    QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;\n    int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;\n    int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;\n    Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;\n    QJsonDocument json() const;\n\nprivate:\n    QJsonValue genJson(QJsonTreeItem *) const;\n\n    QJsonTreeItem *mRootItem;\n    QStringList mHeaders;\n};\n"
  },
  {
    "path": "3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.cpp",
    "content": "#include \"w_JsonEditor.hpp\"\n\n#include \"main/NekoGui.hpp\"\n\nJsonEditor::JsonEditor(const QJsonObject& rootObject, QWidget* parent) : QDialog(parent) {\n    setupUi(this);\n    //    QvMessageBusConnect(JsonEditor);\n    //\n    original = rootObject;\n    final = rootObject;\n    QString jsonString = JsonToString(rootObject);\n\n    if (VerifyJsonString(jsonString).isEmpty()) {\n        jsonTree->setModel(&model);\n        model.loadJson(QJsonDocument(rootObject).toJson());\n    } else {\n        QvMessageBoxWarn(this, tr(\"Json Contains Syntax Errors\"),\n                         tr(\"Original Json may contain syntax errors. Json tree is disabled.\"));\n    }\n\n    jsonEditor->setText(JsonToString(rootObject));\n    jsonTree->expandAll();\n    jsonTree->resizeColumnToContents(0);\n}\n\n// QvMessageBusSlotImpl(JsonEditor)\n//         {\n//                 switch (msg)\n//                 {\n//                     MBShowDefaultImpl;\n//                     MBHideDefaultImpl;\n//                     MBRetranslateDefaultImpl;\n//                     case UPDATE_COLORSCHEME:\n//                         break;\n//                 }\n//         }\n\nQJsonObject JsonEditor::OpenEditor() {\n    int resultCode = this->exec();\n    auto string = jsonEditor->toPlainText();\n\n    while (resultCode == QDialog::Accepted && !VerifyJsonString(string).isEmpty()) {\n        if (string.isEmpty()) {\n            resultCode = QDialog::Accepted;\n            final = {};\n            break;\n        }\n        QvMessageBoxWarn(this, tr(\"Json Contains Syntax Errors\"),\n                         tr(\"You must correct these errors before continuing.\"));\n        resultCode = this->exec();\n        string = jsonEditor->toPlainText();\n    }\n\n    return resultCode == QDialog::Accepted ? final : original;\n}\n\nJsonEditor::~JsonEditor() {\n}\n\nvoid JsonEditor::on_jsonEditor_textChanged() {\n    auto string = jsonEditor->toPlainText();\n    auto VerifyResult = VerifyJsonString(string);\n    jsonValidateStatus->setText(VerifyResult);\n\n    if (VerifyResult.isEmpty()) {\n        BLACK(jsonEditor);\n        final = JsonFromString(string);\n        model.loadJson(QJsonDocument(final).toJson());\n        jsonTree->expandAll();\n        jsonTree->resizeColumnToContents(0);\n    } else {\n        RED(jsonEditor);\n    }\n}\n\nvoid JsonEditor::on_formatJsonBtn_clicked() {\n    auto string = jsonEditor->toPlainText();\n    auto VerifyResult = VerifyJsonString(string);\n    jsonValidateStatus->setText(VerifyResult);\n\n    if (VerifyResult.isEmpty()) {\n        BLACK(jsonEditor);\n        jsonEditor->setPlainText(JsonToString(JsonFromString(string)));\n        model.loadJson(QJsonDocument(JsonFromString(string)).toJson());\n        jsonTree->setModel(&model);\n        jsonTree->expandAll();\n        jsonTree->resizeColumnToContents(0);\n    } else {\n        RED(jsonEditor);\n        QvMessageBoxWarn(this, tr(\"Syntax Errors\"),\n                         tr(\"Please fix the JSON errors or remove the comments before continue\"));\n    }\n}\n\nvoid JsonEditor::on_removeCommentsBtn_clicked() {\n    jsonEditor->setPlainText(JsonToString(JsonFromString(jsonEditor->toPlainText())));\n}\n"
  },
  {
    "path": "3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp",
    "content": "#pragma once\n\n#include \"3rdparty/qv2ray/wrapper.hpp\"\n#include \"3rdparty/qv2ray/v2/ui/widgets/common/QJsonModel.hpp\"\n#include \"ui_w_JsonEditor.h\"\n\n#include <QDialog>\n\nclass JsonEditor\n    : public QDialog,\n      private Ui::JsonEditor {\n    Q_OBJECT\n\npublic:\n    explicit JsonEditor(const QJsonObject& rootObject, QWidget* parent = nullptr);\n    ~JsonEditor();\n    QJsonObject OpenEditor();\n\nprivate slots:\n    void on_jsonEditor_textChanged();\n\n    void on_formatJsonBtn_clicked();\n\n    void on_removeCommentsBtn_clicked();\n\nprivate:\n    QJsonModel model;\n    QJsonObject original;\n    QJsonObject final;\n};\n"
  },
  {
    "path": "3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>JsonEditor</class>\n <widget class=\"QDialog\" name=\"JsonEditor\">\n  <property name=\"windowModality\">\n   <enum>Qt::ApplicationModal</enum>\n  </property>\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>889</width>\n    <height>572</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>JSON Editor</string>\n  </property>\n  <property name=\"modal\">\n   <bool>true</bool>\n  </property>\n  <layout class=\"QGridLayout\" name=\"gridLayout_3\">\n   <item row=\"0\" column=\"0\" colspan=\"2\">\n    <widget class=\"QSplitter\" name=\"splitter\">\n     <property name=\"orientation\">\n      <enum>Qt::Horizontal</enum>\n     </property>\n     <property name=\"childrenCollapsible\">\n      <bool>false</bool>\n     </property>\n     <widget class=\"QWidget\" name=\"layoutWidgetx\">\n      <layout class=\"QGridLayout\" name=\"gridLayout\">\n       <item row=\"1\" column=\"0\" colspan=\"2\">\n        <widget class=\"QTextEdit\" name=\"jsonEditor\">\n         <property name=\"font\">\n          <font>\n           <family>Monospace</family>\n          </font>\n         </property>\n         <property name=\"lineWrapMode\">\n          <enum>QTextEdit::NoWrap</enum>\n         </property>\n         <property name=\"acceptRichText\">\n          <bool>false</bool>\n         </property>\n        </widget>\n       </item>\n       <item row=\"2\" column=\"0\">\n        <widget class=\"QPushButton\" name=\"formatJsonBtn\">\n         <property name=\"text\">\n          <string>Format JSON</string>\n         </property>\n        </widget>\n       </item>\n       <item row=\"2\" column=\"1\">\n        <widget class=\"QPushButton\" name=\"removeCommentsBtn\">\n         <property name=\"text\">\n          <string>Remove All Comments</string>\n         </property>\n        </widget>\n       </item>\n       <item row=\"0\" column=\"0\" colspan=\"2\">\n        <widget class=\"QLabel\" name=\"label\">\n         <property name=\"text\">\n          <string>Json Editor</string>\n         </property>\n        </widget>\n       </item>\n      </layout>\n     </widget>\n     <widget class=\"QWidget\" name=\"layoutWidget\">\n      <layout class=\"QGridLayout\" name=\"gridLayout_2\">\n       <item row=\"0\" column=\"0\">\n        <widget class=\"QLabel\" name=\"label_2\">\n         <property name=\"text\">\n          <string>Structure Preview</string>\n         </property>\n        </widget>\n       </item>\n       <item row=\"1\" column=\"0\">\n        <widget class=\"QTreeView\" name=\"jsonTree\">\n         <property name=\"editTriggers\">\n          <set>QAbstractItemView::NoEditTriggers</set>\n         </property>\n         <property name=\"alternatingRowColors\">\n          <bool>true</bool>\n         </property>\n         <property name=\"indentation\">\n          <number>15</number>\n         </property>\n         <property name=\"uniformRowHeights\">\n          <bool>true</bool>\n         </property>\n         <property name=\"allColumnsShowFocus\">\n          <bool>true</bool>\n         </property>\n         <attribute name=\"headerCascadingSectionResizes\">\n          <bool>true</bool>\n         </attribute>\n         <attribute name=\"headerMinimumSectionSize\">\n          <number>132</number>\n         </attribute>\n         <attribute name=\"headerDefaultSectionSize\">\n          <number>152</number>\n         </attribute>\n        </widget>\n       </item>\n      </layout>\n     </widget>\n    </widget>\n   </item>\n   <item row=\"1\" column=\"0\">\n    <widget class=\"QLabel\" name=\"jsonValidateStatus\">\n     <property name=\"text\">\n      <string>OK</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"1\" column=\"1\">\n    <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n     <property name=\"orientation\">\n      <enum>Qt::Horizontal</enum>\n     </property>\n     <property name=\"standardButtons\">\n      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <tabstops>\n  <tabstop>jsonEditor</tabstop>\n  <tabstop>formatJsonBtn</tabstop>\n  <tabstop>removeCommentsBtn</tabstop>\n  <tabstop>jsonTree</tabstop>\n </tabstops>\n <resources/>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>accepted()</signal>\n   <receiver>JsonEditor</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>248</x>\n     <y>254</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>157</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>rejected()</signal>\n   <receiver>JsonEditor</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>316</x>\n     <y>260</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>286</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.cpp",
    "content": "#include \"GeositeReader.hpp\"\n\n#include \"3rdparty/qv2ray/wrapper.hpp\"\n#include \"picoproto.hpp\"\n\n#include <QFile>\n#include <QMap>\n\nnamespace Qv2ray::components::GeositeReader {\n    QMap<QString, QStringList> GeositeEntries;\n\n    QStringList ReadGeoSiteFromFile(const QString &filepath, bool allowCache) {\n        if (GeositeEntries.contains(filepath) && allowCache)\n            return GeositeEntries.value(filepath);\n\n        QStringList list;\n        qInfo() << \"Reading geosites from:\" << filepath;\n        QFile f(filepath);\n        bool opened = f.open(QFile::OpenModeFlag::ReadOnly);\n\n        if (!opened) {\n            qInfo() << \"File cannot be opened:\" << filepath;\n            return list;\n        }\n\n        const auto content = f.readAll();\n        f.close();\n        {\n            picoproto::Message root;\n            root.ParseFromBytes((unsigned char *) content.data(), content.size());\n\n            list.reserve(root.GetMessageArray(1).size());\n            for (const auto &geosite: root.GetMessageArray(1))\n                list << QString::fromStdString(geosite->GetString(1));\n        }\n\n        qInfo() << \"Loaded\" << list.count() << \"geosite entries from data file.\";\n        list.sort();\n        GeositeEntries[filepath] = list;\n        return list;\n    }\n} // namespace Qv2ray::components::GeositeReader\n"
  },
  {
    "path": "3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.hpp",
    "content": "#pragma once\n\n#include <QString>\n\nnamespace Qv2ray::components::GeositeReader {\n    QStringList ReadGeoSiteFromFile(const QString &filepath, bool allowCache = true);\n} // namespace Qv2ray::components::GeositeReader\n"
  },
  {
    "path": "3rdparty/qv2ray/v3/components/GeositeReader/picoproto.cpp",
    "content": "/* Copyright 2016 Pete Warden. All Rights Reserved.\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#include \"picoproto.hpp\"\n\nnamespace picoproto {\n\n    namespace {\n\n        // To keep the dependencies down, here's a local copy of the widespread bit_cast\n        // operator. This is necessary because in practice weird things can happen if\n        // you just try to use reinterpret_cast.\n        template<class Dest, class Source>\n        inline Dest bit_cast(const Source &source) {\n            static_assert(sizeof(Dest) == sizeof(Source), \"Sizes do not match\");\n            Dest dest;\n            memcpy(&dest, &source, sizeof(dest));\n            return dest;\n        }\n\n        // These are defined in:\n        // https://developers.google.com/protocol-buffers/docs/encoding\n        enum WireType {\n            WIRETYPE_VARINT = 0,\n            WIRETYPE_64BIT = 1,\n            WIRETYPE_LENGTH_DELIMITED = 2,\n            WIRETYPE_GROUP_START = 3,\n            WIRETYPE_GROUP_END = 4,\n            WIRETYPE_32BIT = 5,\n        };\n\n        // Pull bytes from the stream, updating the state.\n        bool ConsumeBytes(uint8_t **current, size_t how_many, size_t *remaining) {\n            if (how_many > *remaining) {\n                PP_LOG(ERROR) << \"ReadBytes overrun!\";\n                return false;\n            }\n            *current += how_many;\n            *remaining -= how_many;\n            return true;\n        }\n\n        // Grabs a particular type from the byte stream.\n        template<class T>\n        T ReadFromBytes(uint8_t **current, size_t *remaining) {\n            PP_CHECK(ConsumeBytes(current, sizeof(T), remaining));\n            const T result = *(bit_cast<T *>(*current - sizeof(T)));\n            return result;\n        }\n\n        uint64_t ReadVarInt(uint8_t **current, size_t *remaining) {\n            uint64_t result = 0;\n            bool keep_going;\n            int shift = 0;\n            do {\n                const uint8_t next_number = ReadFromBytes<uint8_t>(current, remaining);\n                keep_going = (next_number >= 128);\n                result += (uint64_t) (next_number & 0x7f) << shift;\n                shift += 7;\n            } while (keep_going);\n            return result;\n        }\n\n        void ReadWireTypeAndFieldNumber(uint8_t **current, size_t *remaining, uint8_t *wire_type, uint32_t *field_number) {\n            uint64_t wire_type_and_field_number = ReadVarInt(current, remaining);\n            *wire_type = wire_type_and_field_number & 0x07;\n            *field_number = wire_type_and_field_number >> 3;\n        }\n\n    } // namespace\n\n    std::string FieldTypeDebugString(enum FieldType type) {\n        switch (type) {\n            case FIELD_UNSET:\n                return \"UNSET\";\n                break;\n            case FIELD_UINT32:\n                return \"UINT32\";\n                break;\n            case FIELD_UINT64:\n                return \"UINT64\";\n                break;\n            case FIELD_BYTES:\n                return \"BYTES\";\n                break;\n            default:\n                return \"Unknown field type\";\n                break;\n        }\n        return \"Should never get here\";\n    }\n\n    Field::Field(FieldType type, bool owns_data) : type(type), owns_data(owns_data) {\n        cached_messages = nullptr;\n        switch (type) {\n            case FIELD_UINT32: {\n                value.v_uint32 = new std::vector<uint32_t>();\n            } break;\n            case FIELD_UINT64: {\n                value.v_uint64 = new std::vector<uint64_t>();\n            } break;\n            case FIELD_BYTES: {\n                value.v_bytes = new std::vector<std::pair<uint8_t *, size_t>>();\n                cached_messages = new std::vector<Message *>();\n            } break;\n            default: {\n                PP_LOG(ERROR) << \"Bad field type when constructing field: \" << type;\n            } break;\n        }\n    }\n\n    Field::Field(const Field &other) : type(other.type), owns_data(other.owns_data) {\n        switch (type) {\n            case FIELD_UINT32: {\n                value.v_uint32 = new std::vector<uint32_t>(*other.value.v_uint32);\n            } break;\n            case FIELD_UINT64: {\n                value.v_uint64 = new std::vector<uint64_t>(*other.value.v_uint64);\n            } break;\n            case FIELD_BYTES: {\n                if (owns_data) {\n                    value.v_bytes = new std::vector<std::pair<uint8_t *, size_t>>();\n                    for (std::pair<uint8_t *, size_t> data_info: *other.value.v_bytes) {\n                        uint8_t *new_data = new uint8_t[data_info.second];\n                        std::copy_n(data_info.first, data_info.second, new_data);\n                        value.v_bytes->push_back({new_data, data_info.second});\n                    }\n                } else {\n                    value.v_bytes = new std::vector<std::pair<uint8_t *, size_t>>(*other.value.v_bytes);\n                }\n                cached_messages = new std::vector<Message *>();\n                cached_messages->reserve(other.cached_messages->size());\n                for (Message *other_cached_message: *other.cached_messages) {\n                    Message *cached_message;\n                    if (other_cached_message) {\n                        cached_message = new Message(*other_cached_message);\n                    } else {\n                        cached_message = nullptr;\n                    }\n                    cached_messages->push_back(cached_message);\n                }\n            } break;\n            default: {\n                PP_LOG(ERROR) << \"Bad field type when constructing field: \" << type;\n            } break;\n        }\n    }\n\n    Field::~Field() {\n        switch (type) {\n            case FIELD_UINT32:\n                delete value.v_uint32;\n                break;\n            case FIELD_UINT64:\n                delete value.v_uint64;\n                break;\n            case FIELD_BYTES: {\n                if (owns_data)\n                    for (std::pair<uint8_t *, size_t> data_info: *value.v_bytes)\n                        delete[] data_info.first;\n                delete value.v_bytes;\n\n                for (Message *cached_message: *cached_messages)\n                    if (cached_message)\n                        delete cached_message;\n                delete cached_messages;\n                break;\n            }\n            default:\n                PP_LOG(ERROR) << \"Bad field type when destroying field: \" << type;\n                break;\n        }\n    }\n\n    Message::Message() : Message(true){};\n\n    Message::Message(bool copy_arrays) : copy_arrays(copy_arrays){};\n\n    Message::Message(const Message &other) : field_map(other.field_map), fields(other.fields), copy_arrays(other.copy_arrays){};\n\n    Message::~Message(){};\n\n    bool Message::ParseFromBytes(uint8_t *bytes, size_t bytes_size) {\n        uint8_t *current = bytes;\n        size_t remaining = bytes_size;\n        while (remaining > 0) {\n            uint8_t wire_type;\n            uint32_t field_number;\n            ReadWireTypeAndFieldNumber(&current, &remaining, &wire_type, &field_number);\n            switch (wire_type) {\n                case WIRETYPE_VARINT: {\n                    Field *field = AddField(field_number, FIELD_UINT64);\n                    const uint64_t varint = ReadVarInt(&current, &remaining);\n                    field->value.v_uint64->push_back(varint);\n                    break;\n                }\n                case WIRETYPE_64BIT: {\n                    Field *field = AddField(field_number, FIELD_UINT64);\n                    const uint64_t value = ReadFromBytes<uint64_t>(&current, &remaining);\n                    field->value.v_uint64->push_back(value);\n                    break;\n                }\n                case WIRETYPE_LENGTH_DELIMITED: {\n                    Field *field = AddField(field_number, FIELD_BYTES);\n                    const uint64_t size = ReadVarInt(&current, &remaining);\n                    uint8_t *data;\n                    if (copy_arrays) {\n                        data = new uint8_t[size];\n                        std::copy_n(current, size, data);\n                        field->owns_data = true;\n                    } else {\n                        data = current;\n                        field->owns_data = false;\n                    }\n                    field->value.v_bytes->push_back({data, size});\n                    field->cached_messages->push_back(nullptr);\n                    current += size;\n                    remaining -= size;\n                    break;\n                }\n                case WIRETYPE_GROUP_START: {\n                    PP_LOG(INFO) << field_number << \": GROUPSTART\" << std::endl;\n                    PP_LOG(ERROR) << \"Unhandled wire type encountered\";\n                    break;\n                }\n                case WIRETYPE_GROUP_END: {\n                    PP_LOG(INFO) << field_number << \": GROUPEND\" << std::endl;\n                    PP_LOG(ERROR) << \"Unhandled wire type encountered\";\n                    break;\n                }\n                case WIRETYPE_32BIT: {\n                    Field *field = AddField(field_number, FIELD_UINT32);\n                    const uint32_t value = ReadFromBytes<uint32_t>(&current, &remaining);\n                    field->value.v_uint32->push_back(value);\n                    break;\n                }\n                default: {\n                    PP_LOG(ERROR) << \"Unknown wire type encountered: \" << static_cast<int>(wire_type) << \" at offset\" << (bytes_size - remaining);\n                    return false;\n                    break;\n                }\n            }\n        }\n        return true;\n    }\n\n    Field *Message::AddField(int32_t number, enum FieldType type) {\n        Field *field = GetField(number);\n        if (!field) {\n            fields.push_back(Field(type, copy_arrays));\n            field = &fields.back();\n            field_map.insert({number, fields.size() - 1});\n        }\n        return field;\n    }\n\n    Field *Message::GetField(int32_t number) {\n        if (field_map.count(number) == 0)\n            return nullptr;\n        return &(fields[field_map[number]]);\n    }\n\n    Field *Message::GetFieldAndCheckType(int32_t number, enum FieldType type) {\n        Field *field = GetField(number);\n        PP_CHECK(field) << \"No field for \" << number;\n        PP_CHECK(field->type == type) << \"For field \" << number << \" wanted type \" << FieldTypeDebugString(type) << \" but found \" << FieldTypeDebugString(field->type);\n        return field;\n    }\n\n    int32_t Message::GetInt32(int32_t number) {\n        Field *field = GetFieldAndCheckType(number, FIELD_UINT32);\n        uint32_t first_value = (*(field->value.v_uint32))[0];\n        int32_t zig_zag_decoded = static_cast<int32_t>((first_value >> 1) ^ (-(first_value & 1)));\n        return zig_zag_decoded;\n    }\n\n    int64_t Message::GetInt64(int32_t number) {\n        Field *field = GetFieldAndCheckType(number, FIELD_UINT64);\n        uint64_t first_value = (*(field->value.v_uint64))[0];\n        int64_t zig_zag_decoded = static_cast<int64_t>((first_value >> 1) ^ (-(first_value & 1)));\n        return zig_zag_decoded;\n    }\n\n    uint32_t Message::GetUInt32(int32_t number) {\n        Field *field = GetFieldAndCheckType(number, FIELD_UINT32);\n        uint32_t first_value = (*(field->value.v_uint32))[0];\n        return first_value;\n    }\n\n    uint64_t Message::GetUInt64(int32_t number) {\n        Field *field = GetFieldAndCheckType(number, FIELD_UINT64);\n        uint64_t first_value = (*(field->value.v_uint64))[0];\n        return first_value;\n    }\n\n    int64_t Message::GetInt(int32_t number) {\n        Field *field = GetField(number);\n        PP_CHECK(field) << \"No field for \" << number;\n        PP_CHECK((field->type == FIELD_UINT32) || (field->type == FIELD_UINT64))\n            << \"For field \" << number << \" wanted integer type but found \" << FieldTypeDebugString(field->type);\n        switch (field->type) {\n            case FIELD_UINT32:\n                return GetInt32(number);\n                break;\n            case FIELD_UINT64:\n                return GetInt64(number);\n                break;\n            default: {\n                // Should never get here.\n            } break;\n        }\n        // Should never get here.\n        return 0;\n    }\n\n    bool Message::GetBool(int32_t number) {\n        return (GetInt(number) != 0);\n    }\n\n    float Message::GetFloat(int32_t number) {\n        uint32_t int_value = GetUInt32(number);\n        float float_value = *(bit_cast<float *>(&int_value));\n        return float_value;\n    }\n\n    double Message::GetDouble(int32_t number) {\n        uint64_t int_value = GetUInt64(number);\n        return *(bit_cast<double *>(&int_value));\n    }\n\n    std::pair<uint8_t *, size_t> Message::GetBytes(int32_t number) {\n        Field *field = GetFieldAndCheckType(number, FIELD_BYTES);\n        std::pair<uint8_t *, size_t> first_value = (*(field->value.v_bytes))[0];\n        return first_value;\n    }\n\n    std::string Message::GetString(int32_t number) {\n        Field *field = GetFieldAndCheckType(number, FIELD_BYTES);\n        std::pair<uint8_t *, size_t> first_value = (*(field->value.v_bytes))[0];\n        std::string result(first_value.first, first_value.first + first_value.second);\n        return result;\n    }\n\n    Message *Message::GetMessage(int32_t number) {\n        Field *field = GetFieldAndCheckType(number, FIELD_BYTES);\n        Message *cached_message = field->cached_messages->at(0);\n        if (!cached_message) {\n            std::pair<uint8_t *, size_t> first_value = (*(field->value.v_bytes))[0];\n            cached_message = new Message(copy_arrays);\n            cached_message->ParseFromBytes(first_value.first, first_value.second);\n            field->cached_messages->at(0) = cached_message;\n        }\n        return cached_message;\n    }\n\n    std::vector<int32_t> Message::GetInt32Array(int32_t number) {\n        std::vector<uint64_t> raw_array = GetUInt64Array(number);\n        std::vector<int32_t> result;\n        result.reserve(raw_array.size());\n        for (uint64_t raw_value: raw_array) {\n            int32_t zig_zag_decoded = static_cast<int32_t>((raw_value >> 1) ^ (-(raw_value & 1)));\n            result.push_back(zig_zag_decoded);\n        }\n        return result;\n    }\n\n    std::vector<int64_t> Message::GetInt64Array(int32_t number) {\n        std::vector<uint64_t> raw_array = GetUInt64Array(number);\n        std::vector<int64_t> result;\n        result.reserve(raw_array.size());\n        for (uint64_t raw_value: raw_array) {\n            int64_t zig_zag_decoded = static_cast<int64_t>((raw_value >> 1) ^ (-(raw_value & 1)));\n            result.push_back(zig_zag_decoded);\n        }\n        return result;\n    }\n\n    std::vector<uint32_t> Message::GetUInt32Array(int32_t number) {\n        std::vector<uint64_t> raw_array = GetUInt64Array(number);\n        std::vector<uint32_t> result;\n        result.reserve(raw_array.size());\n        for (uint64_t raw_value: raw_array) {\n            result.push_back(static_cast<uint32_t>(raw_value));\n        }\n        return result;\n    }\n\n    std::vector<uint64_t> Message::GetUInt64Array(int32_t number) {\n        std::vector<uint64_t> result;\n        Field *field = GetField(number);\n        if (!field) {\n            return result;\n        }\n        if (field->type == FIELD_UINT64) {\n            result.reserve(field->value.v_uint64->size());\n            for (uint64_t value: *field->value.v_uint64) {\n                result.push_back(static_cast<uint64_t>(value));\n            }\n        } else if (field->type == FIELD_UINT32) {\n            result.reserve(field->value.v_uint32->size());\n            for (uint32_t value: *field->value.v_uint32) {\n                result.push_back(static_cast<uint64_t>(value));\n            }\n        } else if (field->type == FIELD_BYTES) {\n            for (std::pair<uint8_t *, size_t> data_info: *field->value.v_bytes) {\n                uint8_t *current = data_info.first;\n                size_t remaining = data_info.second;\n                while (remaining > 0) {\n                    const uint64_t varint = ReadVarInt(&current, &remaining);\n                    result.push_back(static_cast<int64_t>(varint));\n                }\n            }\n        } else {\n            PP_LOG(ERROR) << \"Expected field type UINT32, UINT64, or BYTES but got \" << FieldTypeDebugString(field->type);\n        }\n        return result;\n    }\n\n    std::vector<bool> Message::GetBoolArray(int32_t number) {\n        std::vector<uint64_t> raw_array = GetUInt64Array(number);\n        std::vector<bool> result;\n        result.reserve(raw_array.size());\n        for (uint64_t raw_value: raw_array) {\n            result.push_back(raw_value != 0);\n        }\n        return result;\n    }\n\n    std::vector<float> Message::GetFloatArray(int32_t number) {\n        std::vector<float> result;\n        Field *field = GetField(number);\n        if (!field) {\n            return result;\n        }\n        if (field->type == FIELD_UINT32) {\n            result.reserve(field->value.v_uint32->size());\n            for (uint32_t value: *field->value.v_uint32) {\n                result.push_back(bit_cast<float>(value));\n            }\n        } else if (field->type == FIELD_BYTES) {\n            for (std::pair<uint8_t *, size_t> data_info: *field->value.v_bytes) {\n                uint8_t *current = data_info.first;\n                size_t remaining = data_info.second;\n                while (remaining > 0) {\n                    const uint64_t varint = ReadVarInt(&current, &remaining);\n                    const uint32_t varint32 = static_cast<uint32_t>(varint & 0xffffffff);\n                    result.push_back(bit_cast<float>(varint32));\n                }\n            }\n        } else {\n            PP_LOG(ERROR) << \"Expected field type UINT32 or BYTES but got \" << FieldTypeDebugString(field->type);\n        }\n        return result;\n    }\n\n    std::vector<double> Message::GetDoubleArray(int32_t number) {\n        std::vector<double> result;\n        Field *field = GetField(number);\n        if (!field) {\n            return result;\n        }\n        if (field->type == FIELD_UINT64) {\n            result.reserve(field->value.v_uint64->size());\n            for (uint64_t value: *field->value.v_uint64) {\n                result.push_back(bit_cast<double>(value));\n            }\n        } else if (field->type == FIELD_BYTES) {\n            for (std::pair<uint8_t *, size_t> data_info: *field->value.v_bytes) {\n                uint8_t *current = data_info.first;\n                size_t remaining = data_info.second;\n                while (remaining > 0) {\n                    const uint64_t varint = ReadVarInt(&current, &remaining);\n                    result.push_back(bit_cast<double>(varint));\n                }\n            }\n        } else {\n            PP_LOG(ERROR) << \"Expected field type UINT64 or BYTES but got \" << FieldTypeDebugString(field->type);\n        }\n        return result;\n    }\n\n    std::vector<std::pair<uint8_t *, size_t>> Message::GetByteArray(int32_t number) {\n        std::vector<std::pair<uint8_t *, size_t>> result;\n        Field *field = GetField(number);\n        if (!field) {\n            return result;\n        }\n        if (field->type == FIELD_BYTES) {\n            result.reserve(field->value.v_bytes->size());\n            for (std::pair<uint8_t *, size_t> data_info: *field->value.v_bytes) {\n                result.push_back(data_info);\n            }\n        } else {\n            PP_LOG(ERROR) << \"Expected field type BYTES but got \" << FieldTypeDebugString(field->type);\n        }\n        return result;\n    }\n\n    std::vector<std::string> Message::GetStringArray(int32_t number) {\n        std::vector<std::string> result;\n        Field *field = GetField(number);\n        if (!field)\n            return result;\n        if (field->type == FIELD_BYTES) {\n            result.reserve(field->value.v_bytes->size());\n            for (std::pair<uint8_t *, size_t> data_info: *field->value.v_bytes) {\n                result.push_back(std::string(data_info.first, data_info.first + data_info.second));\n            }\n        } else {\n            PP_LOG(ERROR) << \"Expected field type BYTES but got \" << FieldTypeDebugString(field->type);\n        }\n        return result;\n    }\n\n    std::vector<Message *> Message::GetMessageArray(int32_t number) {\n        std::vector<Message *> result;\n        Field *field = GetField(number);\n        if (!field)\n            return result;\n\n        if (field->type == FIELD_BYTES) {\n            result.reserve(field->value.v_bytes->size());\n            for (size_t i = 0; i < field->value.v_bytes->size(); ++i) {\n                Message *cached_message = field->cached_messages->at(i);\n                if (!cached_message) {\n                    std::pair<uint8_t *, size_t> value = field->value.v_bytes->at(i);\n                    cached_message = new Message(copy_arrays);\n                    cached_message->ParseFromBytes(value.first, value.second);\n                    field->cached_messages->at(i) = cached_message;\n                }\n                result.push_back(cached_message);\n            }\n        } else {\n            PP_LOG(ERROR) << \"Expected field type BYTES but got \" << FieldTypeDebugString(field->type);\n        }\n        return result;\n    }\n\n} // namespace picoproto\n"
  },
  {
    "path": "3rdparty/qv2ray/v3/components/GeositeReader/picoproto.hpp",
    "content": "/* Copyright 2016 Pete Warden. All Rights Reserved.\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/*\n See the README for full details, but this module lets you read in protobuf\n encoded files with a minimal code footprint.\n\n It doesn't create classes for each kind of message it encounters, it just has a\n single Message interface that lets you access the members of the protobuf as a\n key/value store. This loses a lot of the convenience of type-checked classes,\n but it does mean that very little code is needed to access data from files.\n\n As a simple example, if you had read a `bytes_size` long file into `bytes` that\n contained a TensorFlow GraphDef proto:\n\n Message graph_def;\n graph_def.ParseFromBytes(bytes, bytes_size);\n\n You can then access the different fields of the GraphDef using the field\n numbers assigned in the .proto file:\n\n std::vector<picoproto::Message*> nodes = graph_def.GetMessageArray(1);\n\n One big difference between this minimal approach and normal protobufs is that\n the calling code has to already know the field number and type of any members\n it's trying to access. Here I know that the `node` field is number 1, and that\n it should contain a repeated list of NodeDefs. Since they are not primitive\n types like numbers or strings, they are accessed as an array of Messages.\n\n Here are the design goals of this module:\n  - Keep the code size tiny (single-digit kilobytes on most platforms).\n  - Minimize memory usage (for example allow in-place references to byte data).\n  - Provide a simple, readable implementation that can be ported easily.\n  - Deserialize all saved protobuf files into a usable representation.\n  - No dependencies other than the standard C++ library.\n  - No build-time support (e.g. protoc) required.\n\n Here's what it's explicitly not offering:\n  - Providing a readable and transparent way of accessing serialized data.\n  - Saving out data to protobuf format.\n\n*/\n\n#ifndef INCLUDE_PICOPROTO_H\n#define INCLUDE_PICOPROTO_H\n\n#include <algorithm>\n#include <cstddef>\n#include <cstdint>\n#include <cstring>\n#include <iostream>\n#include <map>\n#include <string>\n#include <vector>\n\n// To keep dependencies minimal, some bare-bones macros to make logging easier.\n#define PP_LOG(X) PP_LOG_##X\n#define PP_LOG_INFO std::cerr << __FILE__ << \":\" << __LINE__ << \" - INFO: \"\n#define PP_LOG_WARN std::cerr << __FILE__ << \":\" << __LINE__ << \" - WARN: \"\n#define PP_LOG_ERROR std::cerr << __FILE__ << \":\" << __LINE__ << \" - ERROR: \"\n#define PP_CHECK(X) \\\n    if (!(X))       \\\n    PP_LOG(ERROR) << \"PP_CHECK(\" << #X << \") failed. \"\n\nnamespace picoproto {\n\n    // These roughly correspond to the wire types used to save data in protobuf\n    // files. The best reference to understand the full format is:\n    // https://developers.google.com/protocol-buffers/docs/encoding\n    // Because we don't know the bit-depth of VarInts, they're always stored as\n    // uint64 values, which is why there's no specific type for them.\n    enum FieldType {\n        FIELD_UNSET,\n        FIELD_UINT32,\n        FIELD_UINT64,\n        FIELD_BYTES,\n    };\n\n    // Gives a readable name for the field type for logging purposes.\n    std::string FieldTypeDebugString(enum FieldType type);\n\n    // Forward declare the main message class, since fields can contain them.\n    class Message;\n\n    // Fields are the building blocks of messages. They contain the values for each\n    // data member, and handle all the allocation and deallocation of storage.\n    // It's unlikely you'll want to access this class directly, since you'll\n    // normally want to use Message below to pull typed values.\n    class Field {\n    public:\n        // You need to specify the type of a Field on creation, so that the right\n        // storage can be set up for the values. You also need to indicate whether the\n        // underlying memory will be around for the lifetime of the message (in which\n        // case no copies are needed) or whether the class should make copies and take\n        // ownership in case the data goes away.\n        Field(FieldType type, bool owns_data);\n        Field(const Field &other);\n        ~Field();\n\n        enum FieldType type;\n        // I know, this isn't very OOP, but the simplicity of keeping track of a type\n        // and deciding how to initialize and access the data based on that persuaded\n        // me this was the best approach. The `value` member contains whatever data\n        // the field should be holding.\n        union {\n            std::vector<uint32_t> *v_uint32;\n            std::vector<uint64_t> *v_uint64;\n            std::vector<std::pair<uint8_t *, size_t>> *v_bytes;\n        } value;\n        // One of the drawbacks of not requiring .proto files ahead of time is that I\n        // don't know if a length-delimited field contains raw bytes, strings, or\n        // sub-messages. The only time we know that a field should be interpreted as a\n        // message is when client code requests it in that form. Because parsing can\n        // be costly, here we cache the results of any such calls for subsequent\n        // accesses.\n        std::vector<Message *> *cached_messages;\n        // If this is set, then the object will allocate its own storage for\n        // length-delimited values, and copy from the input stream. If you know the\n        // underlying data will be around for the lifetime of the message, you can\n        // save memory and copies by leaving this as false.\n        bool owns_data;\n    };\n\n    // The main interface for loading and accessing serialized protobuf data.\n    class Message {\n    public:\n        // If you're not sure about the lifetime of any binary data you're reading\n        // from, just call this default constructor.\n        Message();\n        // In the case when you're sure the lifetime of the byte stream you'll be\n        // decoding is longer than the lifetime of the message, you can set\n        // copy_arrays to false. This is especially useful if you have a memory\n        // mapped file to read from containing large binary blobs, since you'll skip\n        // a lot of copying and extra allocation.\n        Message(bool copy_arrays);\n        Message(const Message &other);\n        ~Message();\n\n        // Populates fields with all of the data from this stream of bytes.\n        // You can call this repeatedly with new messages, and the results will be\n        // merged together.\n        bool ParseFromBytes(uint8_t *binary, size_t binary_size);\n\n        // These are the accessor functions if you're expecting exactly one value in a\n        // field. As discussed above, the burden is on the client code to know the\n        // field number and type of each member it's trying to access, and so pick the\n        // correct accessor function.\n        // If the field isn't present, this will raise an error, so if it's optional\n        // you should use the array accessors below.\n        int32_t GetInt32(int32_t number);\n        int64_t GetInt64(int32_t number);\n        uint32_t GetUInt32(int32_t number);\n        uint64_t GetUInt64(int32_t number);\n        int64_t GetInt(int32_t number);\n        bool GetBool(int32_t number);\n        float GetFloat(int32_t number);\n        double GetDouble(int32_t number);\n        std::pair<uint8_t *, size_t> GetBytes(int32_t number);\n        std::string GetString(int32_t number);\n        Message *GetMessage(int32_t number);\n\n        // If you're not sure if a value will be present, or if it is repeated, you\n        // should call these array functions. If no such field has been seen, then the\n        // result will be an empty vector, otherwise you'll get back one or more\n        // entries.\n        std::vector<int32_t> GetInt32Array(int32_t number);\n        std::vector<int64_t> GetInt64Array(int32_t number);\n        std::vector<uint32_t> GetUInt32Array(int32_t number);\n        std::vector<uint64_t> GetUInt64Array(int32_t number);\n        std::vector<bool> GetBoolArray(int32_t number);\n        std::vector<float> GetFloatArray(int32_t number);\n        std::vector<double> GetDoubleArray(int32_t number);\n        std::vector<std::pair<uint8_t *, size_t>> GetByteArray(int32_t number);\n        std::vector<std::string> GetStringArray(int32_t number);\n        std::vector<Message *> GetMessageArray(int32_t number);\n\n        // It's unlikely you'll want to access fields directly, but here's an escape\n        // hatch in case you do have to manipulate them more directly.\n        Field *GetField(int32_t number);\n\n    private:\n        // Inserts a new field, updating all the internal data structures.\n        Field *AddField(int32_t number, enum FieldType type);\n\n        Field *GetFieldAndCheckType(int32_t number, enum FieldType type);\n\n        // Maps from a field number to an index in the `fields` vector.\n        std::map<int32_t, size_t> field_map;\n        // The core list of fields that have been parsed.\n        std::vector<Field> fields;\n        bool copy_arrays;\n    };\n\n} // namespace picoproto\n\n#endif // INCLUDE_PICOPROTO_H\n"
  },
  {
    "path": "3rdparty/qv2ray/wrapper.hpp",
    "content": "#pragma once\n\n// Qv2ray wrapper\n\n#include <QJsonDocument>\n#include <QDebug>\n\n#define LOG(...) Qv2ray::base::log_internal(__VA_ARGS__)\n#define DEBUG(...) Qv2ray::base::log_internal(__VA_ARGS__)\nnamespace Qv2ray {\n    namespace base {\n        template<typename... T>\n        inline void log_internal(T... v) {}\n    } // namespace base\n} // namespace Qv2ray\n\n#define JsonToString(a) QJsonObject2QString(a, false)\n#define JsonFromString(a) QString2QJsonObject(a)\n#define QvMessageBoxWarn(a, b, c) MessageBoxWarning(b, c)\n\ninline QString VerifyJsonString(const QString &source) {\n    QJsonParseError error{};\n    QJsonDocument doc = QJsonDocument::fromJson(source.toUtf8(), &error);\n    Q_UNUSED(doc)\n\n    if (error.error == QJsonParseError::NoError) {\n        return \"\";\n    } else {\n        // LOG(\"WARNING: Json parse returns: \" + error.errorString());\n        return error.errorString();\n    }\n}\n\n#define RED(obj)                                 \\\n    {                                            \\\n        auto _temp = obj->palette();             \\\n        _temp.setColor(QPalette::Text, Qt::red); \\\n        obj->setPalette(_temp);                  \\\n    }\n\n#define BLACK(obj) obj->setPalette(QWidget::palette());\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.5)\n\nproject(nekobox VERSION 0.1 LANGUAGES CXX)\n\nset(CMAKE_INCLUDE_CURRENT_DIR ON)\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\n# WINDOWS PDB FILE\nif (WIN32)\n    if (MSVC)\n        set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} /Zi\")\n        set(CMAKE_EXE_LINKER_FLAGS_RELEASE \"${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF\")\n    endif ()\nendif ()\n\n# Find Qt\nif (NOT QT_VERSION_MAJOR)\n    set(QT_VERSION_MAJOR 5)\nendif ()\nfind_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Svg LinguistTools)\n\nif (NKR_CROSS)\n    set_property(TARGET Qt5::moc PROPERTY IMPORTED_LOCATION /usr/bin/moc)\n    set_property(TARGET Qt5::uic PROPERTY IMPORTED_LOCATION /usr/bin/uic)\n    set_property(TARGET Qt5::rcc PROPERTY IMPORTED_LOCATION /usr/bin/rcc)\n    set_property(TARGET Qt5::lrelease PROPERTY IMPORTED_LOCATION /usr/bin/lrelease)\n    set_property(TARGET Qt5::lupdate PROPERTY IMPORTED_LOCATION /usr/bin/lupdate)\nendif ()\n\n#### Platform Variables ####\nif (WIN32)\n    include(\"cmake/windows/windows.cmake\")\nelse ()\n    include(\"cmake/linux/linux.cmake\")\nendif ()\n\n#### default prefix path ####\n\nif (NOT NKR_LIBS)\n    if (NKR_PACKAGE)\n        list(APPEND NKR_LIBS ${CMAKE_SOURCE_DIR}/libs/deps/package)\n    else ()\n        list(APPEND NKR_LIBS ${CMAKE_SOURCE_DIR}/libs/deps/built)\n    endif ()\nendif ()\n\nif (NOT NKR_DISABLE_LIBS)\n    list(APPEND CMAKE_PREFIX_PATH ${NKR_LIBS})\nendif ()\n\nmessage(\"[CMAKE_PREFIX_PATH] ${CMAKE_PREFIX_PATH}\")\n\n# for some cross toolchain\nlist(APPEND CMAKE_FIND_ROOT_PATH ${CMAKE_PREFIX_PATH})\nmessage(\"[CMAKE_FIND_ROOT_PATH] ${CMAKE_FIND_ROOT_PATH}\")\n\n#### NKR ####\n\ninclude(\"cmake/print.cmake\")\ninclude(\"cmake/nkr.cmake\")\n\nfind_package(Threads)\n\n#### NKR EXTERNAL ####\n\nif (NKR_NO_EXTERNAL)\n    set(NKR_NO_GRPC 1)\n    set(NKR_NO_YAML 1)\n    set(NKR_NO_ZXING 1)\n    set(NKR_NO_QHOTKEY 1)\nendif ()\n\n# grpc\nif (NKR_NO_GRPC)\n    nkr_add_compile_definitions(NKR_NO_GRPC)\nelse ()\n    # My proto\n    include(\"cmake/myproto.cmake\")\n    list(APPEND NKR_EXTERNAL_TARGETS myproto)\nendif ()\n\n# yaml-cpp\nif (NKR_NO_YAML)\n    nkr_add_compile_definitions(NKR_NO_YAML)\nelse ()\n    find_package(yaml-cpp CONFIG REQUIRED) # only Release is built\n    list(APPEND NKR_EXTERNAL_TARGETS yaml-cpp)\nendif ()\n\n# zxing-cpp\nif (NKR_NO_ZXING)\n    nkr_add_compile_definitions(NKR_NO_ZXING)\nelse ()\n    find_package(ZXing CONFIG REQUIRED)\n    list(APPEND NKR_EXTERNAL_TARGETS ZXing::ZXing)\nendif ()\n\n# QHotkey (static submodule)\nif (NKR_NO_QHOTKEY)\n    nkr_add_compile_definitions(NKR_NO_QHOTKEY)\nelse ()\n    set(QHOTKEY_INSTALL OFF)\n    set(BUILD_SHARED_LIBS OFF)\n    add_subdirectory(3rdparty/QHotkey)\n    list(APPEND NKR_EXTERNAL_TARGETS qhotkey)\nendif ()\n\n#### debug print ####\n\nif (DBG_CMAKE)\n    print_all_variables()\n    print_target_properties(myproto)\n    print_target_properties(yaml-cpp)\n    print_target_properties(ZXing::ZXing)\n    set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE \"${CMAKE_COMMAND} -E time\")\n    set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK \"${CMAKE_COMMAND} -E time\")\nendif ()\n\n# Sources\nset(PROJECT_SOURCES\n        ${PLATFORM_SOURCES}\n\n        main/main.cpp\n        main/NekoGui.cpp\n        main/NekoGui_Utils.cpp\n        main/HTTPRequestHelper.cpp\n\n        3rdparty/base64.cpp\n        3rdparty/qrcodegen.cpp\n        3rdparty/QtExtKeySequenceEdit.cpp\n\n        3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.cpp\n        3rdparty/qv2ray/v2/components/proxy/QvProxyConfigurator.cpp\n        3rdparty/qv2ray/v2/ui/widgets/common/QJsonModel.cpp\n        3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.cpp\n        3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp\n        3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.ui\n\n        3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.cpp\n        3rdparty/qv2ray/v3/components/GeositeReader/picoproto.cpp\n\n        rpc/gRPC.cpp\n\n        db/Database.cpp\n        db/traffic/TrafficLooper.cpp\n        db/ProfileFilter.cpp\n        db/ConfigBuilder.cpp\n\n        fmt/AbstractBean.cpp\n        fmt/Bean2CoreObj_box.cpp\n        fmt/Bean2External.cpp\n        fmt/Bean2Link.cpp\n        fmt/Link2Bean.cpp\n        fmt/ChainBean.hpp # translate\n\n        sub/GroupUpdater.cpp\n\n        sys/ExternalProcess.cpp\n        sys/AutoRun.cpp\n\n        ui/ThemeManager.cpp\n        ui/Icon.cpp\n\n        ui/mainwindow_grpc.cpp\n        ui/mainwindow.cpp\n        ui/mainwindow.h\n        ui/mainwindow.ui\n\n        ui/edit/dialog_edit_profile.h\n        ui/edit/dialog_edit_profile.cpp\n        ui/edit/dialog_edit_profile.ui\n        ui/edit/dialog_edit_group.h\n        ui/edit/dialog_edit_group.cpp\n        ui/edit/dialog_edit_group.ui\n\n        ui/edit/edit_chain.h\n        ui/edit/edit_chain.cpp\n        ui/edit/edit_chain.ui\n        ui/edit/edit_socks_http.h\n        ui/edit/edit_socks_http.cpp\n        ui/edit/edit_socks_http.ui\n        ui/edit/edit_shadowsocks.h\n        ui/edit/edit_shadowsocks.cpp\n        ui/edit/edit_shadowsocks.ui\n        ui/edit/edit_vmess.h\n        ui/edit/edit_vmess.cpp\n        ui/edit/edit_vmess.ui\n        ui/edit/edit_trojan_vless.h\n        ui/edit/edit_trojan_vless.cpp\n        ui/edit/edit_trojan_vless.ui\n\n        ui/edit/edit_naive.h\n        ui/edit/edit_naive.cpp\n        ui/edit/edit_naive.ui\n\n        ui/edit/edit_quic.h\n        ui/edit/edit_quic.cpp\n        ui/edit/edit_quic.ui\n\n        ui/edit/edit_custom.h\n        ui/edit/edit_custom.cpp\n        ui/edit/edit_custom.ui\n\n        ui/dialog_basic_settings.cpp\n        ui/dialog_basic_settings.h\n        ui/dialog_basic_settings.ui\n\n        ui/dialog_manage_groups.cpp\n        ui/dialog_manage_groups.h\n        ui/dialog_manage_groups.ui\n\n        ui/dialog_manage_routes.cpp\n        ui/dialog_manage_routes.h\n        ui/dialog_manage_routes.ui\n\n        ui/dialog_vpn_settings.cpp\n        ui/dialog_vpn_settings.h\n        ui/dialog_vpn_settings.ui\n\n        ui/dialog_hotkey.cpp\n        ui/dialog_hotkey.h\n        ui/dialog_hotkey.ui\n\n        ui/widget/ProxyItem.cpp\n        ui/widget/ProxyItem.h\n        ui/widget/ProxyItem.ui\n        ui/widget/GroupItem.cpp\n        ui/widget/GroupItem.h\n        ui/widget/GroupItem.ui\n\n        res/neko.qrc\n        res/theme/feiyangqingyun/qss.qrc\n        ${QV2RAY_RC}\n)\n\n# Qt exe\nif (${QT_VERSION_MAJOR} GREATER_EQUAL 6)\n    qt_add_executable(nekobox\n            MANUAL_FINALIZATION\n            ${PROJECT_SOURCES}\n    )\n    # Define target properties for Android with Qt 6 as:\n    #    set_property(TARGET nekobox APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR\n    #                 ${CMAKE_CURRENT_SOURCE_DIR}/android)\n    # For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation\nelse ()\n    if (ANDROID)\n        add_library(nekobox SHARED\n                ${PROJECT_SOURCES}\n        )\n        # Define properties for Android with Qt 5 after find_package() calls as:\n        #    set(ANDROID_PACKAGE_SOURCE_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/android\")\n    else ()\n        add_executable(nekobox\n                ${PROJECT_SOURCES}\n        )\n    endif ()\nendif ()\n\n# Target\n\nset_property(TARGET nekobox PROPERTY AUTOUIC ON)\nset_property(TARGET nekobox PROPERTY AUTOMOC ON)\nset_property(TARGET nekobox PROPERTY AUTORCC ON)\n\nset_target_properties(nekobox PROPERTIES\n        WIN32_EXECUTABLE TRUE\n)\n\n# Target Source Translations\n\nset(TS_FILES\n        translations/zh_CN.ts\n        translations/fa_IR.ts\n        translations/ru_RU.ts\n)\nset(LUPDATE_OPTIONS\n        -locations none -no-obsolete\n)\nif (${QT_VERSION_MAJOR} GREATER_EQUAL 6)\n    qt_add_lupdate(nekobox TS_FILES ${TS_FILES} OPTIONS ${LUPDATE_OPTIONS})\n    qt_add_lrelease(nekobox TS_FILES ${TS_FILES} QM_FILES_OUTPUT_VARIABLE QM_FILES)\nelse ()\n    qt5_create_translation(QM_FILES ${PROJECT_SOURCES} ${TS_FILES} OPTIONS ${LUPDATE_OPTIONS})\nendif ()\nconfigure_file(translations/translations.qrc ${CMAKE_BINARY_DIR} COPYONLY)\ntarget_sources(nekobox PRIVATE ${CMAKE_BINARY_DIR}/translations.qrc)\n\n# Target Link\n\ntarget_link_libraries(nekobox PRIVATE\n        Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Svg\n        Threads::Threads\n        ${NKR_EXTERNAL_TARGETS}\n        ${PLATFORM_LIBRARIES}\n)\n\nif (QT_VERSION_MAJOR EQUAL 6)\n    qt_finalize_executable(nekobox)\nendif ()\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "# NekoBox For PC\n\nQt based cross-platform GUI proxy configuration manager (backend: sing-box)\n\nSupport Windows / Linux out of the box now.\n\n基于 Qt 的跨平台代理配置管理器 (后端 sing-box)\n\n目前支持 Windows / Linux 开箱即用\n\n## 下载 / Download\n\n### GitHub Releases (Portable ZIP)\n\n便携格式，无安装器。转到 Releases 下载预编译的二进制文件，解压后即可使用。\n\n[![GitHub All Releases](https://img.shields.io/github/downloads/Matsuridayo/nekoray/total?label=downloads-total&logo=github&style=flat-square)](https://github.com/Matsuridayo/nekoray/releases)\n\n[下载 / Download](https://github.com/Matsuridayo/nekoray/releases)\n\n[安装包的说明，如果你不知道要下载哪一个](https://github.com/MatsuriDayo/nekoray/wiki/Installation-package-description)\n\n### Package\n\n#### AUR\n\n- [nekoray](https://aur.archlinux.org/packages/nekoray)\n- [nekoray-git](https://aur.archlinux.org/packages/nekoray-git)\n\n#### archlinuxcn\n\n- [nekoray](https://github.com/archlinuxcn/repo/tree/master/archlinuxcn/nekoray)\n- [nekoray-git](https://github.com/archlinuxcn/repo/tree/master/archlinuxcn/nekoray-git)\n\n#### Scoop Extras\n\n`scoop install nekoray`\n\n## 更改记录 & 发布频道 / Changelog & Telegram Channel\n\nhttps://t.me/Matsuridayo\n\n## 项目主页 & 文档 / Homepage & Documents\n\nhttps://matsuridayo.github.io\n\n## 代理 / Proxy\n\n- SOCKS (4/4a/5)\n- HTTP(S)\n- Shadowsocks\n- VMess\n- VLESS\n- Trojan\n- TUIC ( sing-box )\n- NaïveProxy ( Custom Core )\n- Hysteria2 ( Custom Core or sing-box )\n- Custom Outbound\n- Custom Config\n- Custom Core\n\n## 订阅 / Subscription\n\n- Raw: some widely used formats (like Shadowsocks, Clash and v2rayN)\n- 原始格式: 一些广泛使用的格式 (如 Shadowsocks、Clash 和 v2rayN)\n\n## 运行参数\n\n[运行参数](docs/RunFlags.md)\n\n## Windows 运行\n\n若提示 DLL 缺失，无法运行，请下载 安装 [微软 C++ 运行库](https://aka.ms/vs/17/release/vc_redist.x64.exe)\n\n## Linux 运行\n\n[Linux 运行教程](docs/Run_Linux.md)\n\n## 编译教程 / Compile Tutorial\n\n请看 [技术文档 / Technical documentation](https://github.com/MatsuriDayo/nekoray/tree/main/docs)\n\n## 捐助 / Donate\n\n如果这个项目对您有帮助，可以通过捐赠的方式帮助我们维持这个项目。\n\n捐赠满等额 50 USD 可以在「[捐赠榜](https://mtrdnt.pages.dev/donation_list)」显示头像，如果您未被添加到这里，欢迎联系我们补充。\n\nDonations of 50 USD or more can display your avatar on the [Donation List](https://mtrdnt.pages.dev/donation_list). If you are not added here, please contact us to add it.\n\nUSDT TRC20\n\n`TRhnA7SXE5Sap5gSG3ijxRmdYFiD4KRhPs`\n\nXMR\n\n`49bwESYQjoRL3xmvTcjZKHEKaiGywjLYVQJMUv79bXonGiyDCs8AzE3KiGW2ytTybBCpWJUvov8SjZZEGg66a4e59GXa6k5`\n\n## Credits\n\nCore:\n\n- [v2fly/v2ray-core](https://github.com/v2fly/v2ray-core) ( < 3.10 )\n- [MatsuriDayo/Matsuri](https://github.com/MatsuriDayo/Matsuri) ( < 3.10 )\n- [MatsuriDayo/v2ray-core](https://github.com/MatsuriDayo/v2ray-core) ( < 3.10 )\n- [XTLS/Xray-core](https://github.com/XTLS/Xray-core) ( 3.10 <= Version <= 3.26 )\n- [MatsuriDayo/Xray-core](https://github.com/MatsuriDayo/Xray-core) ( 3.10 <= Version <= 3.26 )\n- [SagerNet/sing-box](https://github.com/SagerNet/sing-box)\n- [Matsuridayo/sing-box-extra](https://github.com/MatsuriDayo/sing-box-extra)\n\nGui:\n\n- [Qv2ray](https://github.com/Qv2ray/Qv2ray)\n- [Qt](https://www.qt.io/)\n- [protobuf](https://github.com/protocolbuffers/protobuf)\n- [yaml-cpp](https://github.com/jbeder/yaml-cpp)\n- [zxing-cpp](https://github.com/nu-book/zxing-cpp)\n- [QHotkey](https://github.com/Skycoder42/QHotkey)\n- [AppImageKit](https://github.com/AppImage/AppImageKit)\n"
  },
  {
    "path": "cmake/linux/linux.cmake",
    "content": "set(PLATFORM_SOURCES sys/linux/LinuxCap.cpp)\nset(PLATFORM_LIBRARIES dl)\n"
  },
  {
    "path": "cmake/myproto.cmake",
    "content": "find_package(Protobuf CONFIG REQUIRED)\n\nset(PROTO_FILES\n        go/grpc_server/gen/libcore.proto\n        )\n\nadd_library(myproto STATIC ${PROTO_FILES})\ntarget_link_libraries(myproto\n        PUBLIC\n        protobuf::libprotobuf\n        )\ntarget_include_directories(myproto PUBLIC ${CMAKE_CURRENT_BINARY_DIR})\n\nprotobuf_generate(TARGET myproto LANGUAGE cpp)\n"
  },
  {
    "path": "cmake/nkr.cmake",
    "content": "# Release\nfile(STRINGS nekoray_version.txt NKR_VERSION)\nadd_compile_definitions(NKR_VERSION=\\\"${NKR_VERSION}\\\")\n\n# Debug\nset(CMAKE_CXX_FLAGS_DEBUG \"${CMAKE_CXX_FLAGS_DEBUG} -DNKR_CPP_DEBUG\")\n\n# Func\nfunction(nkr_add_compile_definitions arg)\n    message(\"[add_compile_definitions] ${ARGV}\")\n    add_compile_definitions(${ARGV})\nendfunction()\n"
  },
  {
    "path": "cmake/print.cmake",
    "content": "macro(print_all_variables)\n    message(STATUS \"print_all_variables------------------------------------------{\")\n    get_cmake_property(_variableNames VARIABLES)\n    foreach (_variableName ${_variableNames})\n        message(STATUS \"${_variableName}=${${_variableName}}\")\n    endforeach()\n    message(STATUS \"print_all_variables------------------------------------------}\")\nendmacro()\n\n# Get all propreties that cmake supports\nif(NOT CMAKE_PROPERTY_LIST)\n    execute_process(COMMAND cmake --help-property-list OUTPUT_VARIABLE CMAKE_PROPERTY_LIST)\n\n    # Convert command output into a CMake list\n    string(REGEX REPLACE \";\" \"\\\\\\\\;\" CMAKE_PROPERTY_LIST \"${CMAKE_PROPERTY_LIST}\")\n    string(REGEX REPLACE \"\\n\" \";\" CMAKE_PROPERTY_LIST \"${CMAKE_PROPERTY_LIST}\")\nendif()\n\nfunction(print_properties)\n    message(\"CMAKE_PROPERTY_LIST = ${CMAKE_PROPERTY_LIST}\")\nendfunction()\n\nfunction(print_target_properties target)\n    if(NOT TARGET ${target})\n        message(STATUS \"There is no target named '${target}'\")\n        return()\n    endif()\n\n    foreach(property ${CMAKE_PROPERTY_LIST})\n        string(REPLACE \"<CONFIG>\" \"${CMAKE_BUILD_TYPE}\" property ${property})\n\n        # Fix https://stackoverflow.com/questions/32197663/how-can-i-remove-the-the-location-property-may-not-be-read-from-target-error-i\n        if(property STREQUAL \"LOCATION\" OR property MATCHES \"^LOCATION_\" OR property MATCHES \"_LOCATION$\")\n            continue()\n        endif()\n\n        get_property(was_set TARGET ${target} PROPERTY ${property} SET)\n        if(was_set)\n            get_target_property(value ${target} ${property})\n            message(\"${target} ${property} = ${value}\")\n        endif()\n    endforeach()\nendfunction()\n"
  },
  {
    "path": "cmake/windows/VersionInfo.in",
    "content": "#pragma once\n\n#ifndef PRODUCT_VERSION_MAJOR\n    #define PRODUCT_VERSION_MAJOR @PRODUCT_VERSION_MAJOR@\n#endif\n\n#ifndef PRODUCT_VERSION_MINOR\n    #define PRODUCT_VERSION_MINOR @PRODUCT_VERSION_MINOR@\n#endif\n\n#ifndef PRODUCT_VERSION_PATCH\n    #define PRODUCT_VERSION_PATCH @PRODUCT_VERSION_PATCH@\n#endif\n\n#ifndef PRODUCT_VERSION_BUILD\n    #define PRODUCT_VERSION_BUILD @PRODUCT_VERSION_REVISION@\n#endif\n\n#ifndef FILE_VERSION_MAJOR\n    #define FILE_VERSION_MAJOR @PRODUCT_VERSION_MAJOR@\n#endif\n\n#ifndef FILE_VERSION_MINOR\n    #define FILE_VERSION_MINOR @PRODUCT_VERSION_MINOR@\n#endif\n\n#ifndef FILE_VERSION_PATCH\n    #define FILE_VERSION_PATCH @PRODUCT_VERSION_PATCH@\n#endif\n\n#ifndef FILE_VERSION_BUILD\n    #define FILE_VERSION_BUILD @PRODUCT_VERSION_REVISION@\n#endif\n\n#ifndef __TO_STRING\n    #define __TO_STRING_IMPL(x) #x\n    #define __TO_STRING(x) __TO_STRING_IMPL(x)\n#endif\n\n#define PRODUCT_VERSION_MAJOR_MINOR_STR        __TO_STRING(PRODUCT_VERSION_MAJOR) \".\" __TO_STRING(PRODUCT_VERSION_MINOR)\n#define PRODUCT_VERSION_MAJOR_MINOR_PATCH_STR  PRODUCT_VERSION_MAJOR_MINOR_STR \".\" __TO_STRING(PRODUCT_VERSION_PATCH)\n#define PRODUCT_VERSION_FULL_STR               PRODUCT_VERSION_MAJOR_MINOR_PATCH_STR \".\" __TO_STRING(PRODUCT_VERSION_BUILD)\n#define PRODUCT_VERSION_RESOURCE               PRODUCT_VERSION_MAJOR,PRODUCT_VERSION_MINOR,PRODUCT_VERSION_PATCH,PRODUCT_VERSION_BUILD\n#define PRODUCT_VERSION_RESOURCE_STR           PRODUCT_VERSION_FULL_STR \"\\0\"\n\n#define FILE_VERSION_MAJOR_MINOR_STR        __TO_STRING(FILE_VERSION_MAJOR) \".\" __TO_STRING(FILE_VERSION_MINOR)\n#define FILE_VERSION_MAJOR_MINOR_PATCH_STR  FILE_VERSION_MAJOR_MINOR_STR \".\" __TO_STRING(FILE_VERSION_PATCH)\n#define FILE_VERSION_FULL_STR               FILE_VERSION_MAJOR_MINOR_PATCH_STR \".\" __TO_STRING(FILE_VERSION_BUILD)\n#define FILE_VERSION_RESOURCE               FILE_VERSION_MAJOR,FILE_VERSION_MINOR,FILE_VERSION_PATCH,FILE_VERSION_BUILD\n#define FILE_VERSION_RESOURCE_STR           FILE_VERSION_FULL_STR \"\\0\"\n\n#ifndef PRODUCT_ICON\n    #define PRODUCT_ICON \"@PRODUCT_ICON@\"\n#endif\n\n#ifndef PRODUCT_COMMENTS\n    #define PRODUCT_COMMENTS           \"@PRODUCT_COMMENTS@\\0\"\n#endif\n\n#ifndef PRODUCT_COMPANY_NAME\n    #define PRODUCT_COMPANY_NAME       \"@PRODUCT_COMPANY_NAME@\\0\"\n#endif\n\n#ifndef PRODUCT_COMPANY_COPYRIGHT\n    #define PRODUCT_COMPANY_COPYRIGHT  \"@PRODUCT_COMPANY_COPYRIGHT@\\0\"\n#endif\n\n#ifndef PRODUCT_FILE_DESCRIPTION\n    #define PRODUCT_FILE_DESCRIPTION   \"@PRODUCT_FILE_DESCRIPTION@\\0\"\n#endif\n\n#ifndef PRODUCT_INTERNAL_NAME\n    #define PRODUCT_INTERNAL_NAME      \"@PRODUCT_NAME@\\0\"\n#endif\n\n#ifndef PRODUCT_ORIGINAL_FILENAME\n    #define PRODUCT_ORIGINAL_FILENAME  \"@PRODUCT_ORIGINAL_FILENAME@\\0\"\n#endif\n\n#ifndef PRODUCT_BUNDLE\n    #define PRODUCT_BUNDLE             \"@PRODUCT_BUNDLE@\\0\"\n#endif\n"
  },
  {
    "path": "cmake/windows/VersionResource.rc",
    "content": "#include \"VersionInfo.h\"\n\n#if defined(__MINGW64__) || defined(__MINGW32__)\n\t// MinGW-w64, MinGW\n\t#if defined(__has_include) && __has_include(<winres.h>)\n\t\t#include <winres.h>\n\t#else\n\t\t#include <afxres.h>\n\t\t#include <winresrc.h>\n\t#endif\n#else\n\t// MSVC, Windows SDK\n\t#include <winres.h>\n#endif\n\nIDI_ICON1               ICON                    PRODUCT_ICON\n\nLANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT\n\nVS_VERSION_INFO VERSIONINFO\n    FILEVERSION FILE_VERSION_RESOURCE\n    PRODUCTVERSION PRODUCT_VERSION_RESOURCE\n    FILEFLAGSMASK 0x3fL\n#ifdef _DEBUG\n    FILEFLAGS 0x1L\n#else\n    FILEFLAGS 0x0L\n#endif\n    FILEOS 0x4L\n    FILETYPE 0x1L\n    FILESUBTYPE 0x0L\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"000904b0\"\n        BEGIN\n            VALUE \"Comments\", PRODUCT_COMMENTS\n            VALUE \"CompanyName\", PRODUCT_COMPANY_NAME\n            VALUE \"FileDescription\", PRODUCT_FILE_DESCRIPTION\n            VALUE \"FileVersion\", FILE_VERSION_RESOURCE_STR\n            VALUE \"InternalName\", PRODUCT_INTERNAL_NAME\n            VALUE \"LegalCopyright\", PRODUCT_COMPANY_COPYRIGHT\n            VALUE \"OriginalFilename\", PRODUCT_ORIGINAL_FILENAME\n            VALUE \"ProductName\", PRODUCT_BUNDLE\n            VALUE \"ProductVersion\", PRODUCT_VERSION_RESOURCE_STR\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x9, 1200\n    END\nEND\n"
  },
  {
    "path": "cmake/windows/generate_product_version.cmake",
    "content": "include (CMakeParseArguments)\n\nset (GenerateProductVersionCurrentDir ${CMAKE_CURRENT_LIST_DIR})\n\n# generate_product_version() function\n#\n# This function uses VersionInfo.in template file and VersionResource.rc file\n# to generate WIN32 resource with version information and general resource strings.\n#\n# Usage:\n#   generate_product_version(\n#     SomeOutputResourceVariable\n#     NAME MyGreatProject\n#     ICON ${PATH_TO_APP_ICON}\n#     VERSION_MAJOR 2\n#     VERSION_MINOR 3\n#     VERSION_PATCH ${BUILD_COUNTER}\n#     VERSION_REVISION ${BUILD_REVISION}\n#   )\n# where BUILD_COUNTER and BUILD_REVISION could be values from your CI server.\n#\n# You can use generated resource for your executable targets:\n#   add_executable(target-name ${target-files} ${SomeOutputResourceVariable})\n#\n# You can specify resource strings in arguments:\n#   NAME               - name of executable (no defaults, ex: Microsoft Word)\n#   BUNDLE             - bundle (${NAME} is default, ex: Microsoft Office)\n#   ICON               - path to application icon (${CMAKE_SOURCE_DIR}/product.ico by default)\n#   VERSION_MAJOR      - 1 is default\n#   VERSION_MINOR      - 0 is default\n#   VERSION_PATCH      - 0 is default\n#   VERSION_REVISION   - 0 is default\n#   COMPANY_NAME       - your company name (no defaults)\n#   COMPANY_COPYRIGHT  - ${COMPANY_NAME} (C) Copyright ${CURRENT_YEAR} is default\n#   COMMENTS           - ${NAME} v${VERSION_MAJOR}.${VERSION_MINOR} is default\n#   ORIGINAL_FILENAME  - ${NAME} is default\n#   INTERNAL_NAME      - ${NAME} is default\n#   FILE_DESCRIPTION   - ${NAME} is default\nfunction(generate_product_version outfiles)\n    set (options)\n    set (oneValueArgs\n        NAME\n        BUNDLE\n        ICON\n        VERSION_MAJOR\n        VERSION_MINOR\n        VERSION_PATCH\n        VERSION_REVISION\n        COMPANY_NAME\n        COMPANY_COPYRIGHT\n        COMMENTS\n        ORIGINAL_FILENAME\n        INTERNAL_NAME\n        FILE_DESCRIPTION)\n    set (multiValueArgs)\n    cmake_parse_arguments(PRODUCT \"${options}\" \"${oneValueArgs}\" \"${multiValueArgs}\" ${ARGN})\n\n    if (NOT PRODUCT_BUNDLE OR \"${PRODUCT_BUNDLE}\" STREQUAL \"\")\n        set(PRODUCT_BUNDLE \"${PRODUCT_NAME}\")\n    endif()\n    if (NOT PRODUCT_ICON OR \"${PRODUCT_ICON}\" STREQUAL \"\")\n        set(PRODUCT_ICON \"${CMAKE_SOURCE_DIR}/product.ico\")\n    endif()\n\n    if (NOT PRODUCT_VERSION_MAJOR EQUAL 0 AND (NOT PRODUCT_VERSION_MAJOR OR \"${PRODUCT_VERSION_MAJOR}\" STREQUAL \"\"))\n        set(PRODUCT_VERSION_MAJOR 1)\n    endif()\n    if (NOT PRODUCT_VERSION_MINOR EQUAL 0 AND (NOT PRODUCT_VERSION_MINOR OR \"${PRODUCT_VERSION_MINOR}\" STREQUAL \"\"))\n        set(PRODUCT_VERSION_MINOR 0)\n    endif()\n    if (NOT PRODUCT_VERSION_PATCH EQUAL 0 AND (NOT PRODUCT_VERSION_PATCH OR \"${PRODUCT_VERSION_PATCH}\" STREQUAL \"\"))\n        set(PRODUCT_VERSION_PATCH 0)\n    endif()\n    if (NOT PRODUCT_VERSION_REVISION EQUAL 0 AND (NOT PRODUCT_VERSION_REVISION OR \"${PRODUCT_VERSION_REVISION}\" STREQUAL \"\"))\n        set(PRODUCT_VERSION_REVISION 0)\n    endif()\n\n    if (NOT PRODUCT_COMPANY_COPYRIGHT OR \"${PRODUCT_COMPANY_COPYRIGHT}\" STREQUAL \"\")\n        string(TIMESTAMP PRODUCT_CURRENT_YEAR \"%Y\")\n        set(PRODUCT_COMPANY_COPYRIGHT \"${PRODUCT_COMPANY_NAME} (C) Copyright ${PRODUCT_CURRENT_YEAR}\")\n    endif()\n    if (NOT PRODUCT_COMMENTS OR \"${PRODUCT_COMMENTS}\" STREQUAL \"\")\n        set(PRODUCT_COMMENTS \"${PRODUCT_NAME} v${PRODUCT_VERSION_MAJOR}.${PRODUCT_VERSION_MINOR}\")\n    endif()\n    if (NOT PRODUCT_ORIGINAL_FILENAME OR \"${PRODUCT_ORIGINAL_FILENAME}\" STREQUAL \"\")\n        set(PRODUCT_ORIGINAL_FILENAME \"${PRODUCT_NAME}\")\n    endif()\n    if (NOT PRODUCT_INTERNAL_NAME OR \"${PRODUCT_INTERNAL_NAME}\" STREQUAL \"\")\n        set(PRODUCT_INTERNAL_NAME \"${PRODUCT_NAME}\")\n    endif()\n    if (NOT PRODUCT_FILE_DESCRIPTION OR \"${PRODUCT_FILE_DESCRIPTION}\" STREQUAL \"\")\n        set(PRODUCT_FILE_DESCRIPTION \"${PRODUCT_NAME}\")\n    endif()\n\n    set (_VersionInfoFile ${CMAKE_CURRENT_BINARY_DIR}/VersionInfo.h)\n    set (_VersionResourceFile ${CMAKE_CURRENT_BINARY_DIR}/VersionResource.rc)\n    configure_file(\n        ${GenerateProductVersionCurrentDir}/VersionInfo.in\n        ${_VersionInfoFile}\n        @ONLY)\n    configure_file(\n        ${GenerateProductVersionCurrentDir}/VersionResource.rc\n        ${_VersionResourceFile}\n        COPYONLY)\n    list(APPEND ${outfiles} ${_VersionInfoFile} ${_VersionResourceFile})\n    set (${outfiles} ${${outfiles}} PARENT_SCOPE)\nendfunction()\n"
  },
  {
    "path": "cmake/windows/windows.cmake",
    "content": "set(PLATFORM_SOURCES 3rdparty/WinCommander.cpp sys/windows/guihelper.cpp sys/windows/MiniDump.cpp)\nset(PLATFORM_LIBRARIES wininet wsock32 ws2_32 user32 rasapi32 iphlpapi)\n\ninclude(cmake/windows/generate_product_version.cmake)\ngenerate_product_version(\n        QV2RAY_RC\n        ICON \"${CMAKE_SOURCE_DIR}/res/nekobox.ico\"\n        NAME \"nekobox\"\n        BUNDLE \"nekobox\"\n        COMPANY_NAME \"nekobox\"\n        COMPANY_COPYRIGHT \"nekobox\"\n        FILE_DESCRIPTION \"nekobox\"\n)\nadd_definitions(-DUNICODE -D_UNICODE -DNOMINMAX)\nset(GUI_TYPE WIN32)\nif (MINGW)\n    if (NOT DEFINED MinGW_ROOT)\n        set(MinGW_ROOT \"C:/msys64/mingw64\")\n    endif ()\nelse ()\n    add_compile_options(\"/utf-8\")\n    add_compile_options(\"/std:c++17\")\n    add_definitions(-D_WIN32_WINNT=0x600 -D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS)\nendif ()\n"
  },
  {
    "path": "db/ConfigBuilder.cpp",
    "content": "#include \"db/ConfigBuilder.hpp\"\n#include \"db/Database.hpp\"\n#include \"fmt/includes.h\"\n#include \"fmt/Preset.hpp\"\n\n#include <QApplication>\n#include <QFile>\n#include <QFileInfo>\n\n#define BOX_UNDERLYING_DNS dataStore->core_box_underlying_dns.isEmpty() ? \"local\" : dataStore->core_box_underlying_dns\n\nnamespace NekoGui {\n\n    QStringList getAutoBypassExternalProcessPaths(const std::shared_ptr<BuildConfigResult> &result) {\n        QStringList paths;\n        for (const auto &extR: result->extRs) {\n            auto path = extR->program;\n            if (path.trimmed().isEmpty()) continue;\n            paths << path.replace(\"\\\\\", \"/\");\n        }\n        return paths;\n    }\n\n    QString genTunName() {\n        auto tun_name = \"neko-tun\";\n#ifdef Q_OS_MACOS\n        tun_name = \"utun9\";\n#endif\n        return tun_name;\n    }\n\n    void MergeJson(QJsonObject &dst, const QJsonObject &src) {\n        // 合并\n        if (src.isEmpty()) return;\n        for (const auto &key: src.keys()) {\n            auto v_src = src[key];\n            if (dst.contains(key)) {\n                auto v_dst = dst[key];\n                if (v_src.isObject() && v_dst.isObject()) { // isObject 则合并？\n                    auto v_src_obj = v_src.toObject();\n                    auto v_dst_obj = v_dst.toObject();\n                    MergeJson(v_dst_obj, v_src_obj);\n                    dst[key] = v_dst_obj;\n                } else {\n                    dst[key] = v_src;\n                }\n            } else if (v_src.isArray()) {\n                if (key.startsWith(\"+\")) {\n                    auto key2 = SubStrAfter(key, \"+\");\n                    auto v_dst = dst[key2];\n                    auto v_src_arr = v_src.toArray();\n                    auto v_dst_arr = v_dst.toArray();\n                    QJSONARRAY_ADD(v_src_arr, v_dst_arr)\n                    dst[key2] = v_src_arr;\n                } else if (key.endsWith(\"+\")) {\n                    auto key2 = SubStrBefore(key, \"+\");\n                    auto v_dst = dst[key2];\n                    auto v_src_arr = v_src.toArray();\n                    auto v_dst_arr = v_dst.toArray();\n                    QJSONARRAY_ADD(v_dst_arr, v_src_arr)\n                    dst[key2] = v_dst_arr;\n                } else {\n                    dst[key] = v_src;\n                }\n            } else {\n                dst[key] = v_src;\n            }\n        }\n    }\n\n    // Common\n\n    std::shared_ptr<BuildConfigResult> BuildConfig(const std::shared_ptr<ProxyEntity> &ent, bool forTest, bool forExport) {\n        auto result = std::make_shared<BuildConfigResult>();\n        auto status = std::make_shared<BuildConfigStatus>();\n        status->ent = ent;\n        status->result = result;\n        status->forTest = forTest;\n        status->forExport = forExport;\n\n        auto customBean = dynamic_cast<NekoGui_fmt::CustomBean *>(ent->bean.get());\n        if (customBean != nullptr && customBean->core == \"internal-full\") {\n            result->coreConfig = QString2QJsonObject(customBean->config_simple);\n        } else {\n            BuildConfigSingBox(status);\n        }\n\n        // apply custom config\n        MergeJson(result->coreConfig, QString2QJsonObject(ent->bean->custom_config));\n\n        return result;\n    }\n\n    QString BuildChain(int chainId, const std::shared_ptr<BuildConfigStatus> &status) {\n        auto group = profileManager->GetGroup(status->ent->gid);\n        if (group == nullptr) {\n            status->result->error = QStringLiteral(\"This profile is not in any group, your data may be corrupted.\");\n            return {};\n        }\n\n        auto resolveChain = [=](const std::shared_ptr<ProxyEntity> &ent) {\n            QList<std::shared_ptr<ProxyEntity>> resolved;\n            if (ent->type == \"chain\") {\n                auto list = ent->ChainBean()->list;\n                std::reverse(std::begin(list), std::end(list));\n                for (auto id: list) {\n                    resolved += profileManager->GetProfile(id);\n                    if (resolved.last() == nullptr) {\n                        status->result->error = QStringLiteral(\"chain missing ent: %1\").arg(id);\n                        break;\n                    }\n                    if (resolved.last()->type == \"chain\") {\n                        status->result->error = QStringLiteral(\"chain in chain is not allowed: %1\").arg(id);\n                        break;\n                    }\n                }\n            } else {\n                resolved += ent;\n            };\n            return resolved;\n        };\n\n        // Make list\n        auto ents = resolveChain(status->ent);\n        if (!status->result->error.isEmpty()) return {};\n\n        if (group->front_proxy_id >= 0) {\n            auto fEnt = profileManager->GetProfile(group->front_proxy_id);\n            if (fEnt == nullptr) {\n                status->result->error = QStringLiteral(\"front proxy ent not found.\");\n                return {};\n            }\n            ents += resolveChain(fEnt);\n            if (!status->result->error.isEmpty()) return {};\n        }\n\n        // BuildChain\n        QString chainTagOut = BuildChainInternal(0, ents, status);\n\n        // Chain ent traffic stat\n        if (ents.length() > 1) {\n            status->ent->traffic_data->id = status->ent->id;\n            status->ent->traffic_data->tag = chainTagOut.toStdString();\n            status->result->outboundStats += status->ent->traffic_data;\n        }\n\n        return chainTagOut;\n    }\n\n#define DOMAIN_USER_RULE                                                             \\\n    for (const auto &line: SplitLinesSkipSharp(dataStore->routing->proxy_domain)) {  \\\n        if (dataStore->routing->dns_routing) status->domainListDNSRemote += line;    \\\n        status->domainListRemote += line;                                            \\\n    }                                                                                \\\n    for (const auto &line: SplitLinesSkipSharp(dataStore->routing->direct_domain)) { \\\n        if (dataStore->routing->dns_routing) status->domainListDNSDirect += line;    \\\n        status->domainListDirect += line;                                            \\\n    }                                                                                \\\n    for (const auto &line: SplitLinesSkipSharp(dataStore->routing->block_domain)) {  \\\n        status->domainListBlock += line;                                             \\\n    }\n\n#define IP_USER_RULE                                                             \\\n    for (const auto &line: SplitLinesSkipSharp(dataStore->routing->block_ip)) {  \\\n        status->ipListBlock += line;                                             \\\n    }                                                                            \\\n    for (const auto &line: SplitLinesSkipSharp(dataStore->routing->proxy_ip)) {  \\\n        status->ipListRemote += line;                                            \\\n    }                                                                            \\\n    for (const auto &line: SplitLinesSkipSharp(dataStore->routing->direct_ip)) { \\\n        status->ipListDirect += line;                                            \\\n    }\n\n    QString BuildChainInternal(int chainId, const QList<std::shared_ptr<ProxyEntity>> &ents,\n                               const std::shared_ptr<BuildConfigStatus> &status) {\n        QString chainTag = \"c-\" + Int2String(chainId);\n        QString chainTagOut;\n        bool muxApplied = false;\n\n        QString pastTag;\n        int pastExternalStat = 0;\n        int index = 0;\n\n        for (const auto &ent: ents) {\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: chainTag=g-(id)\n            auto tagOut = chainTag + \"-\" + Int2String(ent->id);\n\n            // needGlobal: can only contain one?\n            bool needGlobal = false;\n\n            // first profile set as global\n            auto isFirstProfile = index == ents.length() - 1;\n            if (isFirstProfile) {\n                needGlobal = true;\n                tagOut = \"g-\" + Int2String(ent->id);\n            }\n\n            // last profile set as \"proxy\"\n            if (chainId == 0 && index == 0) {\n                needGlobal = false;\n                tagOut = \"proxy\";\n            }\n\n            // ignoreConnTag\n            if (index != 0) {\n                status->result->ignoreConnTag << tagOut;\n            }\n\n            if (needGlobal) {\n                if (status->globalProfiles.contains(ent->id)) {\n                    continue;\n                }\n                status->globalProfiles += ent->id;\n            }\n\n            if (index > 0) {\n                // chain rules: past\n                if (pastExternalStat == 0) {\n                    auto replaced = status->outbounds.last().toObject();\n                    replaced[\"detour\"] = tagOut;\n                    status->outbounds.removeLast();\n                    status->outbounds += replaced;\n                } else {\n                    status->routingRules += QJsonObject{\n                        {\"inbound\", QJsonArray{pastTag + \"-mapping\"}},\n                        {\"outbound\", tagOut},\n                    };\n                }\n            } else {\n                // index == 0 means last profile in chain / not chain\n                chainTagOut = tagOut;\n                status->result->outboundStat = ent->traffic_data;\n            }\n\n            // chain rules: this\n            auto ext_mapping_port = 0;\n            auto ext_socks_port = 0;\n            auto thisExternalStat = ent->bean->NeedExternal(isFirstProfile);\n            if (thisExternalStat < 0) {\n                status->result->error = \"This configuration cannot be set automatically, please try another.\";\n                return {};\n            }\n\n            // determine port\n            if (thisExternalStat > 0) {\n                if (ent->type == \"custom\") {\n                    auto bean = ent->CustomBean();\n                    if (IsValidPort(bean->mapping_port)) {\n                        ext_mapping_port = bean->mapping_port;\n                    } else {\n                        ext_mapping_port = MkPort();\n                    }\n                    if (IsValidPort(bean->socks_port)) {\n                        ext_socks_port = bean->socks_port;\n                    } else {\n                        ext_socks_port = MkPort();\n                    }\n                } else {\n                    ext_mapping_port = MkPort();\n                    ext_socks_port = MkPort();\n                }\n            }\n            if (thisExternalStat == 2) dataStore->need_keep_vpn_off = true;\n            if (thisExternalStat == 1) {\n                // mapping\n                status->inbounds += QJsonObject{\n                    {\"type\", \"direct\"},\n                    {\"tag\", tagOut + \"-mapping\"},\n                    {\"listen\", \"127.0.0.1\"},\n                    {\"listen_port\", ext_mapping_port},\n                    {\"override_address\", ent->bean->serverAddress},\n                    {\"override_port\", ent->bean->serverPort},\n                };\n                // no chain rule and not outbound, so need to set to direct\n                if (isFirstProfile) {\n                    status->routingRules += QJsonObject{\n                        {\"inbound\", QJsonArray{tagOut + \"-mapping\"}},\n                        {\"outbound\", \"direct\"},\n                    };\n                }\n            }\n\n            // Outbound\n\n            QJsonObject outbound;\n            auto stream = GetStreamSettings(ent->bean.get());\n\n            if (thisExternalStat > 0) {\n                auto extR = ent->bean->BuildExternal(ext_mapping_port, ext_socks_port, thisExternalStat);\n                if (extR.program.isEmpty()) {\n                    status->result->error = QObject::tr(\"Core not found: %1\").arg(ent->bean->DisplayCoreType());\n                    return {};\n                }\n                if (!extR.error.isEmpty()) { // rejected\n                    status->result->error = extR.error;\n                    return {};\n                }\n                extR.tag = ent->bean->DisplayType();\n                status->result->extRs.emplace_back(std::make_shared<NekoGui_fmt::ExternalBuildResult>(extR));\n\n                // SOCKS OUTBOUND\n                outbound[\"type\"] = \"socks\";\n                outbound[\"server\"] = \"127.0.0.1\";\n                outbound[\"server_port\"] = ext_socks_port;\n            } else {\n                const auto coreR = ent->bean->BuildCoreObjSingBox();\n                if (coreR.outbound.isEmpty()) {\n                    status->result->error = \"unsupported outbound\";\n                    return {};\n                }\n                if (!coreR.error.isEmpty()) { // rejected\n                    status->result->error = coreR.error;\n                    return {};\n                }\n                outbound = coreR.outbound;\n            }\n\n            // outbound misc\n            outbound[\"tag\"] = tagOut;\n            ent->traffic_data->id = ent->id;\n            ent->traffic_data->tag = tagOut.toStdString();\n            status->result->outboundStats += ent->traffic_data;\n\n            // mux common\n            auto needMux = ent->type == \"vmess\" || ent->type == \"trojan\" || ent->type == \"vless\";\n            needMux &= dataStore->mux_concurrency > 0;\n\n            if (stream != nullptr) {\n                if (stream->network == \"grpc\" || stream->network == \"quic\" || (stream->network == \"http\" && stream->security == \"tls\")) {\n                    needMux = false;\n                }\n                if (stream->multiplex_status == 0) {\n                    if (!dataStore->mux_default_on) needMux = false;\n                } else if (stream->multiplex_status == 1) {\n                    needMux = true;\n                } else if (stream->multiplex_status == 2) {\n                    needMux = false;\n                }\n            }\n            if (ent->type == \"vless\" && outbound[\"flow\"] != \"\") {\n                needMux = false;\n            }\n\n            // common\n            // apply domain_strategy\n            outbound[\"domain_strategy\"] = dataStore->routing->outbound_domain_strategy;\n            // apply mux\n            if (!muxApplied && needMux) {\n                auto muxObj = QJsonObject{\n                    {\"enabled\", true},\n                    {\"protocol\", dataStore->mux_protocol},\n                    {\"padding\", dataStore->mux_padding},\n                    {\"max_streams\", dataStore->mux_concurrency},\n                };\n                outbound[\"multiplex\"] = muxObj;\n                muxApplied = true;\n            }\n\n            // apply custom outbound settings\n            MergeJson(outbound, QString2QJsonObject(ent->bean->custom_outbound));\n\n            // Bypass Lookup for the first profile\n            auto serverAddress = ent->bean->serverAddress;\n\n            auto customBean = dynamic_cast<NekoGui_fmt::CustomBean *>(ent->bean.get());\n            if (customBean != nullptr && customBean->core == \"internal\") {\n                auto server = QString2QJsonObject(customBean->config_simple)[\"server\"].toString();\n                if (!server.isEmpty()) serverAddress = server;\n            }\n\n            if (!IsIpAddress(serverAddress)) {\n                status->domainListDNSDirect += \"full:\" + serverAddress;\n            }\n\n            status->outbounds += outbound;\n            pastTag = tagOut;\n            pastExternalStat = thisExternalStat;\n            index++;\n        }\n\n        return chainTagOut;\n    }\n\n    // SingBox\n\n    void BuildConfigSingBox(const std::shared_ptr<BuildConfigStatus> &status) {\n        // Log\n        status->result->coreConfig[\"log\"] = QJsonObject{{\"level\", dataStore->log_level}};\n\n        // Inbounds\n\n        // mixed-in\n        if (IsValidPort(dataStore->inbound_socks_port) && !status->forTest) {\n            QJsonObject inboundObj;\n            inboundObj[\"tag\"] = \"mixed-in\";\n            inboundObj[\"type\"] = \"mixed\";\n            inboundObj[\"listen\"] = dataStore->inbound_address;\n            inboundObj[\"listen_port\"] = dataStore->inbound_socks_port;\n            if (dataStore->routing->sniffing_mode != SniffingMode::DISABLE) {\n                inboundObj[\"sniff\"] = true;\n                inboundObj[\"sniff_override_destination\"] = dataStore->routing->sniffing_mode == SniffingMode::FOR_DESTINATION;\n            }\n            if (dataStore->inbound_auth->NeedAuth()) {\n                inboundObj[\"users\"] = QJsonArray{\n                    QJsonObject{\n                        {\"username\", dataStore->inbound_auth->username},\n                        {\"password\", dataStore->inbound_auth->password},\n                    },\n                };\n            }\n            inboundObj[\"domain_strategy\"] = dataStore->routing->domain_strategy;\n            status->inbounds += inboundObj;\n        }\n\n        // tun-in\n        if (dataStore->vpn_internal_tun && dataStore->spmode_vpn && !status->forTest) {\n            QJsonObject inboundObj;\n            inboundObj[\"tag\"] = \"tun-in\";\n            inboundObj[\"type\"] = \"tun\";\n            inboundObj[\"interface_name\"] = genTunName();\n            inboundObj[\"auto_route\"] = true;\n            inboundObj[\"endpoint_independent_nat\"] = true;\n            inboundObj[\"mtu\"] = dataStore->vpn_mtu;\n            inboundObj[\"stack\"] = Preset::SingBox::VpnImplementation.value(dataStore->vpn_implementation);\n            inboundObj[\"strict_route\"] = dataStore->vpn_strict_route;\n            inboundObj[\"inet4_address\"] = \"172.19.0.1/28\";\n            if (dataStore->vpn_ipv6) inboundObj[\"inet6_address\"] = \"fdfe:dcba:9876::1/126\";\n            if (dataStore->routing->sniffing_mode != SniffingMode::DISABLE) {\n                inboundObj[\"sniff\"] = true;\n                inboundObj[\"sniff_override_destination\"] = dataStore->routing->sniffing_mode == SniffingMode::FOR_DESTINATION;\n            }\n            inboundObj[\"domain_strategy\"] = dataStore->routing->domain_strategy;\n            status->inbounds += inboundObj;\n        }\n\n        // Outbounds\n        auto tagProxy = BuildChain(0, status);\n        if (!status->result->error.isEmpty()) return;\n\n        // direct & bypass & block\n        status->outbounds += QJsonObject{\n            {\"type\", \"direct\"},\n            {\"tag\", \"direct\"},\n        };\n        status->outbounds += QJsonObject{\n            {\"type\", \"direct\"},\n            {\"tag\", \"bypass\"},\n        };\n        status->outbounds += QJsonObject{\n            {\"type\", \"block\"},\n            {\"tag\", \"block\"},\n        };\n        if (!status->forTest) {\n            status->outbounds += QJsonObject{\n                {\"type\", \"dns\"},\n                {\"tag\", \"dns-out\"},\n            };\n        }\n\n        // custom inbound\n        if (!status->forTest) QJSONARRAY_ADD(status->inbounds, QString2QJsonObject(dataStore->custom_inbound)[\"inbounds\"].toArray())\n\n        status->result->coreConfig.insert(\"inbounds\", status->inbounds);\n        status->result->coreConfig.insert(\"outbounds\", status->outbounds);\n\n        // user rule\n        if (!status->forTest) {\n            DOMAIN_USER_RULE\n            IP_USER_RULE\n        }\n\n        // sing-box common rule object\n        auto make_rule = [&](const QStringList &list, bool isIP = false) {\n            QJsonObject rule;\n            //\n            QJsonArray ip_cidr;\n            QJsonArray geoip;\n            //\n            QJsonArray domain_keyword;\n            QJsonArray domain_subdomain;\n            QJsonArray domain_regexp;\n            QJsonArray domain_full;\n            QJsonArray geosite;\n            for (auto item: list) {\n                if (isIP) {\n                    if (item.startsWith(\"geoip:\")) {\n                        geoip += item.replace(\"geoip:\", \"\");\n                    } else {\n                        ip_cidr += item;\n                    }\n                } else {\n                    // https://www.v2fly.org/config/dns.html#dnsobject\n                    if (item.startsWith(\"geosite:\")) {\n                        geosite += item.replace(\"geosite:\", \"\");\n                    } else if (item.startsWith(\"full:\")) {\n                        domain_full += item.replace(\"full:\", \"\").toLower();\n                    } else if (item.startsWith(\"domain:\")) {\n                        domain_subdomain += item.replace(\"domain:\", \"\").toLower();\n                    } else if (item.startsWith(\"regexp:\")) {\n                        domain_regexp += item.replace(\"regexp:\", \"\").toLower();\n                    } else if (item.startsWith(\"keyword:\")) {\n                        domain_keyword += item.replace(\"keyword:\", \"\").toLower();\n                    } else {\n                        domain_subdomain += item.toLower();\n                    }\n                }\n            }\n            if (isIP) {\n                if (ip_cidr.isEmpty() && geoip.isEmpty()) return rule;\n                rule[\"ip_cidr\"] = ip_cidr;\n                rule[\"geoip\"] = geoip;\n            } else {\n                if (domain_keyword.isEmpty() && domain_subdomain.isEmpty() && domain_regexp.isEmpty() && domain_full.isEmpty() && geosite.isEmpty()) {\n                    return rule;\n                }\n                rule[\"domain\"] = domain_full;\n                rule[\"domain_suffix\"] = domain_subdomain; // v2ray Subdomain => sing-box suffix\n                rule[\"domain_keyword\"] = domain_keyword;\n                rule[\"domain_regex\"] = domain_regexp;\n                rule[\"geosite\"] = geosite;\n            }\n            return rule;\n        };\n\n        // final add DNS\n        QJsonObject dns;\n        QJsonArray dnsServers;\n        QJsonArray dnsRules;\n\n        // Remote\n        if (!status->forTest)\n            dnsServers += QJsonObject{\n                {\"tag\", \"dns-remote\"},\n                {\"address_resolver\", \"dns-local\"},\n                {\"strategy\", dataStore->routing->remote_dns_strategy},\n                {\"address\", dataStore->routing->remote_dns},\n                {\"detour\", tagProxy},\n            };\n\n        // Direct\n        QJsonObject directObj{\n            {\"tag\", \"dns-direct\"},\n            {\"address_resolver\", \"dns-local\"},\n            {\"strategy\", dataStore->routing->direct_dns_strategy},\n            {\"address\", dataStore->routing->direct_dns},\n            {\"detour\", \"direct\"},\n        };\n        if (dataStore->routing->dns_final_out == \"bypass\") {\n            dnsServers.prepend(directObj);\n        } else {\n            dnsServers.append(directObj);\n        }\n        dnsRules.append(QJsonObject{\n            {\"outbound\", \"any\"},\n            {\"server\", \"dns-direct\"},\n        });\n\n        // block\n        if (!status->forTest)\n            dnsServers += QJsonObject{\n                {\"tag\", \"dns-block\"},\n                {\"address\", \"rcode://success\"},\n            };\n\n        // Fakedns\n        if (dataStore->fake_dns && dataStore->vpn_internal_tun && dataStore->spmode_vpn && !status->forTest) {\n            dnsServers += QJsonObject{\n                {\"tag\", \"dns-fake\"},\n                {\"address\", \"fakeip\"},\n            };\n            dns[\"fakeip\"] = QJsonObject{\n                {\"enabled\", true},\n                {\"inet4_range\", \"198.18.0.0/15\"},\n                {\"inet6_range\", \"fc00::/18\"},\n            };\n        }\n\n        // Underlying 100% Working DNS ?\n        dnsServers += QJsonObject{\n            {\"tag\", \"dns-local\"},\n            {\"address\", BOX_UNDERLYING_DNS},\n            {\"detour\", \"direct\"},\n        };\n\n        // sing-box dns rule object\n        auto add_rule_dns = [&](const QStringList &list, const QString &server) {\n            auto rule = make_rule(list, false);\n            if (rule.isEmpty()) return;\n            rule[\"server\"] = server;\n            dnsRules += rule;\n        };\n        add_rule_dns(status->domainListDNSRemote, \"dns-remote\");\n        add_rule_dns(status->domainListDNSDirect, \"dns-direct\");\n\n        // built-in rules\n        if (!status->forTest) {\n            dnsRules += QJsonObject{\n                {\"query_type\", QJsonArray{32, 33}},\n                {\"server\", \"dns-block\"},\n            };\n            dnsRules += QJsonObject{\n                {\"domain_suffix\", \".lan\"},\n                {\"server\", \"dns-block\"},\n            };\n        }\n\n        // fakedns rule\n        if (dataStore->fake_dns && dataStore->vpn_internal_tun && dataStore->spmode_vpn && !status->forTest) {\n            dnsRules += QJsonObject{\n                {\"inbound\", \"tun-in\"},\n                {\"server\", \"dns-fake\"},\n            };\n        }\n\n        dns[\"servers\"] = dnsServers;\n        dns[\"rules\"] = dnsRules;\n        dns[\"independent_cache\"] = true;\n\n        if (dataStore->routing->use_dns_object) {\n            dns = QString2QJsonObject(dataStore->routing->dns_object);\n        }\n        status->result->coreConfig.insert(\"dns\", dns);\n\n        // Routing\n\n        // dns hijack\n        if (!status->forTest) {\n            status->routingRules += QJsonObject{\n                {\"protocol\", \"dns\"},\n                {\"outbound\", \"dns-out\"},\n            };\n        }\n\n        // sing-box routing rule object\n        auto add_rule_route = [&](const QStringList &list, bool isIP, const QString &out) {\n            auto rule = make_rule(list, isIP);\n            if (rule.isEmpty()) return;\n            rule[\"outbound\"] = out;\n            status->routingRules += rule;\n        };\n\n        // final add user rule\n        add_rule_route(status->domainListBlock, false, \"block\");\n        add_rule_route(status->domainListRemote, false, tagProxy);\n        add_rule_route(status->domainListDirect, false, \"bypass\");\n        add_rule_route(status->ipListBlock, true, \"block\");\n        add_rule_route(status->ipListRemote, true, tagProxy);\n        add_rule_route(status->ipListDirect, true, \"bypass\");\n\n        // built-in rules\n        status->routingRules += QJsonObject{\n            {\"network\", \"udp\"},\n            {\"port\", QJsonArray{135, 137, 138, 139, 5353}},\n            {\"outbound\", \"block\"},\n        };\n        status->routingRules += QJsonObject{\n            {\"ip_cidr\", QJsonArray{\"224.0.0.0/3\", \"ff00::/8\"}},\n            {\"outbound\", \"block\"},\n        };\n        status->routingRules += QJsonObject{\n            {\"source_ip_cidr\", QJsonArray{\"224.0.0.0/3\", \"ff00::/8\"}},\n            {\"outbound\", \"block\"},\n        };\n\n        // tun user rule\n        if (dataStore->vpn_internal_tun && dataStore->spmode_vpn && !status->forTest) {\n            auto match_out = dataStore->vpn_rule_white ? \"proxy\" : \"bypass\";\n\n            QString process_name_rule = dataStore->vpn_rule_process.trimmed();\n            if (!process_name_rule.isEmpty()) {\n                auto arr = SplitLinesSkipSharp(process_name_rule);\n                QJsonObject rule{{\"outbound\", match_out},\n                                 {\"process_name\", QList2QJsonArray(arr)}};\n                status->routingRules += rule;\n            }\n\n            QString cidr_rule = dataStore->vpn_rule_cidr.trimmed();\n            if (!cidr_rule.isEmpty()) {\n                auto arr = SplitLinesSkipSharp(cidr_rule);\n                QJsonObject rule{{\"outbound\", match_out},\n                                 {\"ip_cidr\", QList2QJsonArray(arr)}};\n                status->routingRules += rule;\n            }\n\n            auto autoBypassExternalProcessPaths = getAutoBypassExternalProcessPaths(status->result);\n            if (!autoBypassExternalProcessPaths.isEmpty()) {\n                QJsonObject rule{{\"outbound\", \"bypass\"},\n                                 {\"process_name\", QList2QJsonArray(autoBypassExternalProcessPaths)}};\n                status->routingRules += rule;\n            }\n        }\n\n        // geopath\n        auto geoip = FindCoreAsset(\"geoip.db\");\n        auto geosite = FindCoreAsset(\"geosite.db\");\n        if (geoip.isEmpty()) status->result->error = +\"geoip.db not found\";\n        if (geosite.isEmpty()) status->result->error = +\"geosite.db not found\";\n\n        // final add routing rule\n        auto routingRules = QString2QJsonObject(dataStore->routing->custom)[\"rules\"].toArray();\n        if (status->forTest) routingRules = {};\n        if (!status->forTest) QJSONARRAY_ADD(routingRules, QString2QJsonObject(dataStore->custom_route_global)[\"rules\"].toArray())\n        QJSONARRAY_ADD(routingRules, status->routingRules)\n        auto routeObj = QJsonObject{\n            {\"rules\", routingRules},\n            {\"auto_detect_interface\", dataStore->spmode_vpn}, // TODO force enable?\n            {\n                \"geoip\",\n                QJsonObject{\n                    {\"path\", geoip},\n                },\n            },\n            {\n                \"geosite\",\n                QJsonObject{\n                    {\"path\", geosite},\n                },\n            }};\n        if (!status->forTest) routeObj[\"final\"] = dataStore->routing->def_outbound;\n        if (status->forExport) {\n            routeObj.remove(\"geoip\");\n            routeObj.remove(\"geosite\");\n            routeObj.remove(\"auto_detect_interface\");\n        }\n        status->result->coreConfig.insert(\"route\", routeObj);\n\n        // experimental\n        QJsonObject experimentalObj;\n\n        if (!status->forTest && dataStore->core_box_clash_api > 0) {\n            QJsonObject clash_api = {\n                {\"external_controller\", \"127.0.0.1:\" + Int2String(dataStore->core_box_clash_api)},\n                {\"secret\", dataStore->core_box_clash_api_secret},\n                {\"external_ui\", \"dashboard\"},\n            };\n            experimentalObj[\"clash_api\"] = clash_api;\n        }\n\n        if (!experimentalObj.isEmpty()) status->result->coreConfig.insert(\"experimental\", experimentalObj);\n    }\n\n    QString WriteVPNSingBoxConfig() {\n        // tun user rule\n        auto match_out = dataStore->vpn_rule_white ? \"neko-socks\" : \"direct\";\n        auto no_match_out = dataStore->vpn_rule_white ? \"direct\" : \"neko-socks\";\n\n        QString process_name_rule = dataStore->vpn_rule_process.trimmed();\n        if (!process_name_rule.isEmpty()) {\n            auto arr = SplitLinesSkipSharp(process_name_rule);\n            QJsonObject rule{{\"outbound\", match_out},\n                             {\"process_name\", QList2QJsonArray(arr)}};\n            process_name_rule = \",\" + QJsonObject2QString(rule, false);\n        }\n\n        QString cidr_rule = dataStore->vpn_rule_cidr.trimmed();\n        if (!cidr_rule.isEmpty()) {\n            auto arr = SplitLinesSkipSharp(cidr_rule);\n            QJsonObject rule{{\"outbound\", match_out},\n                             {\"ip_cidr\", QList2QJsonArray(arr)}};\n            cidr_rule = \",\" + QJsonObject2QString(rule, false);\n        }\n\n        // TODO bypass ext core process path?\n\n        // auth\n        QString socks_user_pass;\n        if (dataStore->inbound_auth->NeedAuth()) {\n            socks_user_pass = R\"( \"username\": \"%1\", \"password\": \"%2\", )\";\n            socks_user_pass = socks_user_pass.arg(dataStore->inbound_auth->username, dataStore->inbound_auth->password);\n        }\n        // gen config\n        auto configFn = \":/neko/vpn/sing-box-vpn.json\";\n        if (QFile::exists(\"vpn/sing-box-vpn.json\")) configFn = \"vpn/sing-box-vpn.json\";\n        auto config = ReadFileText(configFn)\n                          .replace(\"//%IPV6_ADDRESS%\", dataStore->vpn_ipv6 ? R\"(\"inet6_address\": \"fdfe:dcba:9876::1/126\",)\" : \"\")\n                          .replace(\"//%SOCKS_USER_PASS%\", socks_user_pass)\n                          .replace(\"//%PROCESS_NAME_RULE%\", process_name_rule)\n                          .replace(\"//%CIDR_RULE%\", cidr_rule)\n                          .replace(\"%MTU%\", Int2String(dataStore->vpn_mtu))\n                          .replace(\"%STACK%\", Preset::SingBox::VpnImplementation.value(dataStore->vpn_implementation))\n                          .replace(\"%TUN_NAME%\", genTunName())\n                          .replace(\"%STRICT_ROUTE%\", dataStore->vpn_strict_route ? \"true\" : \"false\")\n                          .replace(\"%FINAL_OUT%\", no_match_out)\n                          .replace(\"%DNS_ADDRESS%\", BOX_UNDERLYING_DNS)\n                          .replace(\"%FAKE_DNS_INBOUND%\", dataStore->fake_dns ? \"tun-in\" : \"empty\")\n                          .replace(\"%PORT%\", Int2String(dataStore->inbound_socks_port));\n        // write config\n        QFile file;\n        file.setFileName(QFileInfo(configFn).fileName());\n        file.open(QIODevice::ReadWrite | QIODevice::Truncate);\n        file.write(config.toUtf8());\n        file.close();\n        return QFileInfo(file).absoluteFilePath();\n    }\n\n    QString WriteVPNLinuxScript(const QString &configPath) {\n#ifdef Q_OS_WIN\n        return {};\n#endif\n        // gen script\n        auto scriptFn = \":/neko/vpn/vpn-run-root.sh\";\n        if (QFile::exists(\"vpn/vpn-run-root.sh\")) scriptFn = \"vpn/vpn-run-root.sh\";\n        auto script = ReadFileText(scriptFn)\n                          .replace(\"./nekobox_core\", QApplication::applicationDirPath() + \"/nekobox_core\")\n                          .replace(\"$CONFIG_PATH\", configPath);\n        // write script\n        QFile file2;\n        file2.setFileName(QFileInfo(scriptFn).fileName());\n        file2.open(QIODevice::ReadWrite | QIODevice::Truncate);\n        file2.write(script.toUtf8());\n        file2.close();\n        return QFileInfo(file2).absoluteFilePath();\n    }\n\n} // namespace NekoGui\n"
  },
  {
    "path": "db/ConfigBuilder.hpp",
    "content": "#pragma once\n\n#include \"ProxyEntity.hpp\"\n#include \"sys/ExternalProcess.hpp\"\n\nnamespace NekoGui {\n    class BuildConfigResult {\n    public:\n        QString error;\n        QJsonObject coreConfig;\n\n        QList<std::shared_ptr<NekoGui_traffic::TrafficData>> outboundStats; // all, but not including \"bypass\" \"block\"\n        std::shared_ptr<NekoGui_traffic::TrafficData> outboundStat;         // main\n        QStringList ignoreConnTag;\n\n        std::list<std::shared_ptr<NekoGui_fmt::ExternalBuildResult>> extRs;\n    };\n\n    class BuildConfigStatus {\n    public:\n        std::shared_ptr<BuildConfigResult> result;\n        std::shared_ptr<ProxyEntity> ent;\n        bool forTest;\n        bool forExport;\n\n        // priv\n        QList<int> globalProfiles;\n\n        // xxList is V2Ray format string list\n\n        QStringList domainListDNSRemote;\n        QStringList domainListDNSDirect;\n        QStringList domainListRemote;\n        QStringList domainListDirect;\n        QStringList ipListRemote;\n        QStringList ipListDirect;\n        QStringList domainListBlock;\n        QStringList ipListBlock;\n\n        // config format\n\n        QJsonArray routingRules;\n        QJsonArray inbounds;\n        QJsonArray outbounds;\n    };\n\n    std::shared_ptr<BuildConfigResult> BuildConfig(const std::shared_ptr<ProxyEntity> &ent, bool forTest, bool forExport);\n\n    void BuildConfigSingBox(const std::shared_ptr<BuildConfigStatus> &status);\n\n    QString BuildChain(int chainId, const std::shared_ptr<BuildConfigStatus> &status);\n\n    QString BuildChainInternal(int chainId, const QList<std::shared_ptr<ProxyEntity>> &ents,\n                               const std::shared_ptr<BuildConfigStatus> &status);\n\n    QString WriteVPNSingBoxConfig();\n\n    QString WriteVPNLinuxScript(const QString &configPath);\n} // namespace NekoGui\n"
  },
  {
    "path": "db/Database.cpp",
    "content": "#include \"Database.hpp\"\n\n#include \"fmt/includes.h\"\n\n#include <QFile>\n#include <QDir>\n#include <QColor>\n\nnamespace NekoGui {\n\n    ProfileManager *profileManager = new ProfileManager();\n\n    ProfileManager::ProfileManager() : JsonStore(\"groups/pm.json\") {\n        _add(new configItem(\"groups\", &groupsTabOrder, itemType::integerList));\n    }\n\n    QList<int> filterIntJsonFile(const QString &path) {\n        QList<int> result;\n        QDir dr(path);\n        auto entryList = dr.entryList(QDir::Files);\n        for (auto e: entryList) {\n            e = e.toLower();\n            if (!e.endsWith(\".json\", Qt::CaseInsensitive)) continue;\n            e = e.remove(\".json\", Qt::CaseInsensitive);\n            bool ok;\n            auto id = e.toInt(&ok);\n            if (ok) {\n                result << id;\n            }\n        }\n        std::sort(result.begin(), result.end());\n        return result;\n    }\n\n    void ProfileManager::LoadManager() {\n        JsonStore::Load();\n        //\n        profiles = {};\n        groups = {};\n        profilesIdOrder = filterIntJsonFile(\"profiles\");\n        groupsIdOrder = filterIntJsonFile(\"groups\");\n        // Load Proxys\n        QList<int> delProfile;\n        for (auto id: profilesIdOrder) {\n            auto ent = LoadProxyEntity(QStringLiteral(\"profiles/%1.json\").arg(id));\n            // Corrupted profile?\n            if (ent == nullptr || ent->bean == nullptr || ent->bean->version == -114514) {\n                delProfile << id;\n                continue;\n            }\n            profiles[id] = ent;\n        }\n        // Clear Corrupted profile\n        for (auto id: delProfile) {\n            DeleteProfile(id);\n        }\n        // Load Groups\n        auto loadedOrder = groupsTabOrder;\n        groupsTabOrder = {};\n        for (auto id: groupsIdOrder) {\n            auto ent = LoadGroup(QStringLiteral(\"groups/%1.json\").arg(id));\n            // Corrupted group?\n            if (ent->id != id) {\n                continue;\n            }\n            // Ensure order contains every group\n            if (!loadedOrder.contains(id)) {\n                loadedOrder << id;\n            }\n            groups[id] = ent;\n        }\n        // Ensure groups contains order\n        for (auto id: loadedOrder) {\n            if (groups.count(id)) {\n                groupsTabOrder << id;\n            }\n        }\n        // First setup\n        if (groups.empty()) {\n            auto defaultGroup = NekoGui::ProfileManager::NewGroup();\n            defaultGroup->name = QObject::tr(\"Default\");\n            NekoGui::profileManager->AddGroup(defaultGroup);\n        }\n        //\n        if (dataStore->flag_reorder) {\n            {\n                // remove all (contains orphan)\n                for (const auto &profile: profiles) {\n                    QFile::remove(profile.second->fn);\n                }\n            }\n            std::map<int, int> gidOld2New;\n            {\n                int i = 0;\n                int ii = 0;\n                QList<int> newProfilesIdOrder;\n                std::map<int, std::shared_ptr<ProxyEntity>> newProfiles;\n                for (auto gid: groupsTabOrder) {\n                    auto group = GetGroup(gid);\n                    gidOld2New[gid] = ii++;\n                    for (auto const &profile: group->ProfilesWithOrder()) {\n                        auto oldId = profile->id;\n                        auto newId = i++;\n                        profile->id = newId;\n                        profile->gid = gidOld2New[gid];\n                        profile->fn = QStringLiteral(\"profiles/%1.json\").arg(newId);\n                        profile->Save();\n                        newProfiles[newId] = profile;\n                        newProfilesIdOrder << newId;\n                    }\n                    group->order = {};\n                    group->Save();\n                }\n                profiles = newProfiles;\n                profilesIdOrder = newProfilesIdOrder;\n            }\n            {\n                QList<int> newGroupsIdOrder;\n                std::map<int, std::shared_ptr<Group>> newGroups;\n                for (auto oldGid: groupsTabOrder) {\n                    auto newId = gidOld2New[oldGid];\n                    auto group = groups[oldGid];\n                    QFile::remove(group->fn);\n                    group->id = newId;\n                    group->fn = QStringLiteral(\"groups/%1.json\").arg(newId);\n                    group->Save();\n                    newGroups[newId] = group;\n                    newGroupsIdOrder << newId;\n                }\n                groups = newGroups;\n                groupsIdOrder = newGroupsIdOrder;\n                groupsTabOrder = newGroupsIdOrder;\n            }\n            MessageBoxInfo(software_name, \"Profiles and groups reorder complete.\");\n        }\n    }\n\n    void ProfileManager::SaveManager() {\n        JsonStore::Save();\n    }\n\n    std::shared_ptr<ProxyEntity> ProfileManager::LoadProxyEntity(const QString &jsonPath) {\n        // Load type\n        ProxyEntity ent0(nullptr, nullptr);\n        ent0.fn = jsonPath;\n        auto validJson = ent0.Load();\n        auto type = ent0.type;\n\n        // Load content\n        std::shared_ptr<ProxyEntity> ent;\n        bool validType = validJson;\n\n        if (validType) {\n            ent = NewProxyEntity(type);\n            validType = ent->bean->version != -114514;\n        }\n\n        if (validType) {\n            ent->load_control_must = true;\n            ent->fn = jsonPath;\n            ent->Load();\n        }\n        return ent;\n    }\n\n    //  新建的不给 fn 和 id\n\n    std::shared_ptr<ProxyEntity> ProfileManager::NewProxyEntity(const QString &type) {\n        NekoGui_fmt::AbstractBean *bean;\n\n        if (type == \"socks\") {\n            bean = new NekoGui_fmt::SocksHttpBean(NekoGui_fmt::SocksHttpBean::type_Socks5);\n        } else if (type == \"http\") {\n            bean = new NekoGui_fmt::SocksHttpBean(NekoGui_fmt::SocksHttpBean::type_HTTP);\n        } else if (type == \"shadowsocks\") {\n            bean = new NekoGui_fmt::ShadowSocksBean();\n        } else if (type == \"chain\") {\n            bean = new NekoGui_fmt::ChainBean();\n        } else if (type == \"vmess\") {\n            bean = new NekoGui_fmt::VMessBean();\n        } else if (type == \"trojan\") {\n            bean = new NekoGui_fmt::TrojanVLESSBean(NekoGui_fmt::TrojanVLESSBean::proxy_Trojan);\n        } else if (type == \"vless\") {\n            bean = new NekoGui_fmt::TrojanVLESSBean(NekoGui_fmt::TrojanVLESSBean::proxy_VLESS);\n        } else if (type == \"naive\") {\n            bean = new NekoGui_fmt::NaiveBean();\n        } else if (type == \"hysteria2\") {\n            bean = new NekoGui_fmt::QUICBean(NekoGui_fmt::QUICBean::proxy_Hysteria2);\n        } else if (type == \"tuic\") {\n            bean = new NekoGui_fmt::QUICBean(NekoGui_fmt::QUICBean::proxy_TUIC);\n        } else if (type == \"custom\") {\n            bean = new NekoGui_fmt::CustomBean();\n        } else {\n            bean = new NekoGui_fmt::AbstractBean(-114514);\n        }\n\n        auto ent = std::make_shared<ProxyEntity>(bean, type);\n        return ent;\n    }\n\n    std::shared_ptr<Group> ProfileManager::NewGroup() {\n        auto ent = std::make_shared<Group>();\n        return ent;\n    }\n\n    // ProxyEntity\n\n    ProxyEntity::ProxyEntity(NekoGui_fmt::AbstractBean *bean, const QString &type_) {\n        if (type_ != nullptr) this->type = type_;\n\n        _add(new configItem(\"type\", &type, itemType::string));\n        _add(new configItem(\"id\", &id, itemType::integer));\n        _add(new configItem(\"gid\", &gid, itemType::integer));\n        _add(new configItem(\"yc\", &latency, itemType::integer));\n        _add(new configItem(\"report\", &full_test_report, itemType::string));\n\n        // 可以不关联 bean，只加载 ProxyEntity 的信息\n        if (bean != nullptr) {\n            this->bean = std::shared_ptr<NekoGui_fmt::AbstractBean>(bean);\n            // 有虚函数就要在这里 dynamic_cast\n            _add(new configItem(\"bean\", dynamic_cast<JsonStore *>(bean), itemType::jsonStore));\n            _add(new configItem(\"traffic\", dynamic_cast<JsonStore *>(traffic_data.get()), itemType::jsonStore));\n        }\n    };\n\n    QString ProxyEntity::DisplayLatency() const {\n        if (latency < 0) {\n            return QObject::tr(\"Unavailable\");\n        } else if (latency > 0) {\n            return UNICODE_LRO + QStringLiteral(\"%1 ms\").arg(latency);\n        } else {\n            return \"\";\n        }\n    }\n\n    QColor ProxyEntity::DisplayLatencyColor() const {\n        if (latency < 0) {\n            return Qt::red;\n        } else if (latency > 0) {\n            auto greenMs = dataStore->test_latency_url.startsWith(\"https://\") ? 200 : 100;\n            if (latency < greenMs) {\n                return Qt::darkGreen;\n            } else {\n                return Qt::darkYellow;\n            }\n        } else {\n            return {};\n        }\n    }\n\n    // Profile\n\n    int ProfileManager::NewProfileID() const {\n        if (profiles.empty()) {\n            return 0;\n        } else {\n            return profilesIdOrder.last() + 1;\n        }\n    }\n\n    bool ProfileManager::AddProfile(const std::shared_ptr<ProxyEntity> &ent, int gid) {\n        if (ent->id >= 0) {\n            return false;\n        }\n\n        ent->gid = gid < 0 ? dataStore->current_group : gid;\n        ent->id = NewProfileID();\n        profiles[ent->id] = ent;\n        profilesIdOrder.push_back(ent->id);\n\n        ent->fn = QStringLiteral(\"profiles/%1.json\").arg(ent->id);\n        ent->Save();\n        return true;\n    }\n\n    void ProfileManager::DeleteProfile(int id) {\n        if (id < 0) return;\n        if (dataStore->started_id == id) return;\n        profiles.erase(id);\n        profilesIdOrder.removeAll(id);\n        QFile(QStringLiteral(\"profiles/%1.json\").arg(id)).remove();\n    }\n\n    void ProfileManager::MoveProfile(const std::shared_ptr<ProxyEntity> &ent, int gid) {\n        if (gid == ent->gid || gid < 0) return;\n        auto oldGroup = GetGroup(ent->gid);\n        if (oldGroup != nullptr && !oldGroup->order.isEmpty()) {\n            oldGroup->order.removeAll(ent->id);\n            oldGroup->Save();\n        }\n        auto newGroup = GetGroup(gid);\n        if (newGroup != nullptr && !newGroup->order.isEmpty()) {\n            newGroup->order.push_back(ent->id);\n            newGroup->Save();\n        }\n        ent->gid = gid;\n        ent->Save();\n    }\n\n    std::shared_ptr<ProxyEntity> ProfileManager::GetProfile(int id) {\n        return profiles.count(id) ? profiles[id] : nullptr;\n    }\n\n    // Group\n\n    Group::Group() {\n        _add(new configItem(\"id\", &id, itemType::integer));\n        _add(new configItem(\"front_proxy_id\", &front_proxy_id, itemType::integer));\n        _add(new configItem(\"archive\", &archive, itemType::boolean));\n        _add(new configItem(\"skip_auto_update\", &skip_auto_update, itemType::boolean));\n        _add(new configItem(\"name\", &name, itemType::string));\n        _add(new configItem(\"order\", &order, itemType::integerList));\n        _add(new configItem(\"url\", &url, itemType::string));\n        _add(new configItem(\"info\", &info, itemType::string));\n        _add(new configItem(\"lastup\", &sub_last_update, itemType::integer64));\n        _add(new configItem(\"manually_column_width\", &manually_column_width, itemType::boolean));\n        _add(new configItem(\"column_width\", &column_width, itemType::integerList));\n    }\n\n    std::shared_ptr<Group> ProfileManager::LoadGroup(const QString &jsonPath) {\n        auto ent = std::make_shared<Group>();\n        ent->fn = jsonPath;\n        ent->Load();\n        return ent;\n    }\n\n    int ProfileManager::NewGroupID() const {\n        if (groups.empty()) {\n            return 0;\n        } else {\n            return groupsIdOrder.last() + 1;\n        }\n    }\n\n    bool ProfileManager::AddGroup(const std::shared_ptr<Group> &ent) {\n        if (ent->id >= 0) {\n            return false;\n        }\n\n        ent->id = NewGroupID();\n        groups[ent->id] = ent;\n        groupsIdOrder.push_back(ent->id);\n        groupsTabOrder.push_back(ent->id);\n\n        ent->fn = QStringLiteral(\"groups/%1.json\").arg(ent->id);\n        ent->Save();\n        return true;\n    }\n\n    void ProfileManager::DeleteGroup(int gid) {\n        if (groups.size() <= 1) return;\n        QList<int> toDelete;\n        for (const auto &[id, profile]: profiles) {\n            if (profile->gid == gid) toDelete += id; // map访问中，不能操作\n        }\n        for (const auto &id: toDelete) {\n            DeleteProfile(id);\n        }\n        groups.erase(gid);\n        groupsIdOrder.removeAll(gid);\n        groupsTabOrder.removeAll(gid);\n        QFile(QStringLiteral(\"groups/%1.json\").arg(gid)).remove();\n    }\n\n    std::shared_ptr<Group> ProfileManager::GetGroup(int id) {\n        return groups.count(id) ? groups[id] : nullptr;\n    }\n\n    std::shared_ptr<Group> ProfileManager::CurrentGroup() {\n        return GetGroup(dataStore->current_group);\n    }\n\n    QList<std::shared_ptr<ProxyEntity>> Group::Profiles() const {\n        QList<std::shared_ptr<ProxyEntity>> ret;\n        for (const auto &[_, profile]: profileManager->profiles) {\n            if (id == profile->gid) ret += profile;\n        }\n        return ret;\n    }\n\n    QList<std::shared_ptr<ProxyEntity>> Group::ProfilesWithOrder() const {\n        if (order.isEmpty()) {\n            return Profiles();\n        } else {\n            QList<std::shared_ptr<ProxyEntity>> ret;\n            for (auto _id: order) {\n                auto ent = profileManager->GetProfile(_id);\n                if (ent != nullptr) ret += ent;\n            }\n            return ret;\n        }\n    }\n\n} // namespace NekoGui\n"
  },
  {
    "path": "db/Database.hpp",
    "content": "#pragma once\n\n#include \"main/NekoGui.hpp\"\n#include \"ProxyEntity.hpp\"\n#include \"Group.hpp\"\n\nnamespace NekoGui {\n    class ProfileManager : private JsonStore {\n    public:\n        // JsonStore\n\n        // order -> id\n        QList<int> groupsTabOrder;\n\n        // Manager\n\n        std::map<int, std::shared_ptr<ProxyEntity>> profiles;\n        std::map<int, std::shared_ptr<Group>> groups;\n\n        ProfileManager();\n\n        // LoadManager Reset and loads profiles & groups\n        void LoadManager();\n\n        void SaveManager();\n\n        [[nodiscard]] static std::shared_ptr<ProxyEntity> NewProxyEntity(const QString &type);\n\n        [[nodiscard]] static std::shared_ptr<Group> NewGroup();\n\n        bool AddProfile(const std::shared_ptr<ProxyEntity> &ent, int gid = -1);\n\n        void DeleteProfile(int id);\n\n        void MoveProfile(const std::shared_ptr<ProxyEntity> &ent, int gid);\n\n        std::shared_ptr<ProxyEntity> GetProfile(int id);\n\n        bool AddGroup(const std::shared_ptr<Group> &ent);\n\n        void DeleteGroup(int gid);\n\n        std::shared_ptr<Group> GetGroup(int id);\n\n        std::shared_ptr<Group> CurrentGroup();\n\n    private:\n        // sort by id\n        QList<int> profilesIdOrder;\n        QList<int> groupsIdOrder;\n\n        [[nodiscard]] int NewProfileID() const;\n\n        [[nodiscard]] int NewGroupID() const;\n\n        static std::shared_ptr<ProxyEntity> LoadProxyEntity(const QString &jsonPath);\n\n        static std::shared_ptr<Group> LoadGroup(const QString &jsonPath);\n    };\n\n    extern ProfileManager *profileManager;\n} // namespace NekoGui\n"
  },
  {
    "path": "db/Group.hpp",
    "content": "#pragma once\n\n#include \"main/NekoGui.hpp\"\n#include \"ProxyEntity.hpp\"\n\nnamespace NekoGui {\n    class Group : public JsonStore {\n    public:\n        int id = -1;\n        bool archive = false;\n        bool skip_auto_update = false;\n        QString name = \"\";\n        QString url = \"\";\n        QString info = \"\";\n        qint64 sub_last_update = 0;\n        int front_proxy_id = -1;\n\n        // list ui\n        bool manually_column_width = false;\n        QList<int> column_width;\n        QList<int> order;\n\n        Group();\n\n        // 按 id 顺序\n        [[nodiscard]] QList<std::shared_ptr<ProxyEntity>> Profiles() const;\n\n        // 按 显示 顺序\n        [[nodiscard]] QList<std::shared_ptr<ProxyEntity>> ProfilesWithOrder() const;\n    };\n} // namespace NekoGui\n"
  },
  {
    "path": "db/ProfileFilter.cpp",
    "content": "#include \"ProfileFilter.hpp\"\n\nnamespace NekoGui {\n\n    QString ProfileFilter_ent_key(const std::shared_ptr<NekoGui::ProxyEntity> &ent, bool by_address) {\n        by_address &= ent->type != \"custom\";\n        return by_address ? (ent->bean->DisplayAddress() + ent->bean->DisplayType())\n                          : QJsonObject2QString(ent->bean->ToJson({\"c_cfg\", \"c_out\"}), true) + ent->bean->DisplayType();\n    }\n\n    void ProfileFilter::Uniq(const QList<std::shared_ptr<ProxyEntity>> &in,\n                             QList<std::shared_ptr<ProxyEntity>> &out,\n                             bool by_address, bool keep_last) {\n        QMap<QString, std::shared_ptr<ProxyEntity>> hashMap;\n\n        for (const auto &ent: in) {\n            QString key = ProfileFilter_ent_key(ent, by_address);\n            if (hashMap.contains(key)) {\n                if (keep_last) {\n                    out.removeAll(hashMap[key]);\n                    hashMap[key] = ent;\n                    out += ent;\n                }\n            } else {\n                hashMap[key] = ent;\n                out += ent;\n            }\n        }\n    }\n\n    void ProfileFilter::Common(const QList<std::shared_ptr<ProxyEntity>> &src,\n                               const QList<std::shared_ptr<ProxyEntity>> &dst,\n                               QList<std::shared_ptr<ProxyEntity>> &outSrc,\n                               QList<std::shared_ptr<ProxyEntity>> &outDst,\n                               bool by_address) {\n        QMap<QString, std::shared_ptr<ProxyEntity>> hashMap;\n\n        for (const auto &ent: src) {\n            QString key = ProfileFilter_ent_key(ent, by_address);\n            hashMap[key] = ent;\n        }\n        for (const auto &ent: dst) {\n            QString key = ProfileFilter_ent_key(ent, by_address);\n            if (hashMap.contains(key)) {\n                outDst += ent;\n                outSrc += hashMap[key];\n            }\n        }\n    }\n\n    void ProfileFilter::OnlyInSrc(const QList<std::shared_ptr<ProxyEntity>> &src,\n                                  const QList<std::shared_ptr<ProxyEntity>> &dst,\n                                  QList<std::shared_ptr<ProxyEntity>> &out,\n                                  bool by_address) {\n        QMap<QString, bool> hashMap;\n\n        for (const auto &ent: dst) {\n            QString key = ProfileFilter_ent_key(ent, by_address);\n            hashMap[key] = true;\n        }\n        for (const auto &ent: src) {\n            QString key = ProfileFilter_ent_key(ent, by_address);\n            if (!hashMap.contains(key)) out += ent;\n        }\n    }\n\n    void ProfileFilter::OnlyInSrc_ByPointer(const QList<std::shared_ptr<ProxyEntity>> &src,\n                                            const QList<std::shared_ptr<ProxyEntity>> &dst,\n                                            QList<std::shared_ptr<ProxyEntity>> &out) {\n        for (const auto &ent: src) {\n            if (!dst.contains(ent)) out += ent;\n        }\n    }\n\n} // namespace NekoGui"
  },
  {
    "path": "db/ProfileFilter.hpp",
    "content": "#pragma once\n\n#include \"ProxyEntity.hpp\"\n\nnamespace NekoGui {\n    class ProfileFilter {\n    public:\n        static void Uniq(\n            const QList<std::shared_ptr<ProxyEntity>> &in,\n            QList<std::shared_ptr<ProxyEntity>> &out,\n            bool by_address = false, // def by bean\n            bool keep_last = false   // def keep first\n        );\n\n        static void Common(\n            const QList<std::shared_ptr<ProxyEntity>> &src,\n            const QList<std::shared_ptr<ProxyEntity>> &dst,\n            QList<std::shared_ptr<ProxyEntity>> &outSrc,\n            QList<std::shared_ptr<ProxyEntity>> &outDst,\n            bool by_address = false // def by bean\n        );\n\n        static void OnlyInSrc(\n            const QList<std::shared_ptr<ProxyEntity>> &src,\n            const QList<std::shared_ptr<ProxyEntity>> &dst,\n            QList<std::shared_ptr<ProxyEntity>> &out,\n            bool by_address = false // def by bean\n        );\n\n        static void OnlyInSrc_ByPointer(\n            const QList<std::shared_ptr<ProxyEntity>> &src,\n            const QList<std::shared_ptr<ProxyEntity>> &dst,\n            QList<std::shared_ptr<ProxyEntity>> &out);\n    };\n} // namespace NekoGui\n"
  },
  {
    "path": "db/ProxyEntity.hpp",
    "content": "#pragma once\n\n#include \"main/NekoGui.hpp\"\n#include \"db/traffic/TrafficData.hpp\"\n#include \"fmt/AbstractBean.hpp\"\n\nnamespace NekoGui_fmt {\n    class SocksHttpBean;\n\n    class ShadowSocksBean;\n\n    class VMessBean;\n\n    class TrojanVLESSBean;\n\n    class NaiveBean;\n\n    class QUICBean;\n\n    class CustomBean;\n\n    class ChainBean;\n}; // namespace NekoGui_fmt\n\nnamespace NekoGui {\n    class ProxyEntity : public JsonStore {\n    public:\n        QString type;\n\n        int id = -1;\n        int gid = 0;\n        int latency = 0;\n        std::shared_ptr<NekoGui_fmt::AbstractBean> bean;\n        std::shared_ptr<NekoGui_traffic::TrafficData> traffic_data = std::make_shared<NekoGui_traffic::TrafficData>(\"\");\n\n        QString full_test_report;\n\n        ProxyEntity(NekoGui_fmt::AbstractBean *bean, const QString &type_);\n\n        [[nodiscard]] QString DisplayLatency() const;\n\n        [[nodiscard]] QColor DisplayLatencyColor() const;\n\n        [[nodiscard]] NekoGui_fmt::ChainBean *ChainBean() const {\n            return (NekoGui_fmt::ChainBean *) bean.get();\n        };\n\n        [[nodiscard]] NekoGui_fmt::SocksHttpBean *SocksHTTPBean() const {\n            return (NekoGui_fmt::SocksHttpBean *) bean.get();\n        };\n\n        [[nodiscard]] NekoGui_fmt::ShadowSocksBean *ShadowSocksBean() const {\n            return (NekoGui_fmt::ShadowSocksBean *) bean.get();\n        };\n\n        [[nodiscard]] NekoGui_fmt::VMessBean *VMessBean() const {\n            return (NekoGui_fmt::VMessBean *) bean.get();\n        };\n\n        [[nodiscard]] NekoGui_fmt::TrojanVLESSBean *TrojanVLESSBean() const {\n            return (NekoGui_fmt::TrojanVLESSBean *) bean.get();\n        };\n\n        [[nodiscard]] NekoGui_fmt::NaiveBean *NaiveBean() const {\n            return (NekoGui_fmt::NaiveBean *) bean.get();\n        };\n\n        [[nodiscard]] NekoGui_fmt::QUICBean *QUICBean() const {\n            return (NekoGui_fmt::QUICBean *) bean.get();\n        };\n\n        [[nodiscard]] NekoGui_fmt::CustomBean *CustomBean() const {\n            return (NekoGui_fmt::CustomBean *) bean.get();\n        };\n    };\n} // namespace NekoGui\n"
  },
  {
    "path": "db/traffic/TrafficData.hpp",
    "content": "#pragma once\n\n#include \"main/NekoGui.hpp\"\n\nnamespace NekoGui_traffic {\n    class TrafficData : public JsonStore {\n    public:\n        int id = -1; // ent id\n        std::string tag;\n\n        long long downlink = 0;\n        long long uplink = 0;\n        long long downlink_rate = 0;\n        long long uplink_rate = 0;\n\n        long long last_update;\n\n        explicit TrafficData(std::string tag) {\n            this->tag = std::move(tag);\n            _add(new configItem(\"dl\", &downlink, itemType::integer64));\n            _add(new configItem(\"ul\", &uplink, itemType::integer64));\n        };\n\n        void Reset() {\n            downlink = 0;\n            uplink = 0;\n            downlink_rate = 0;\n            uplink_rate = 0;\n        }\n\n        [[nodiscard]] QString DisplaySpeed() const {\n            return UNICODE_LRO + QStringLiteral(\"%1↑ %2↓\").arg(ReadableSize(uplink_rate), ReadableSize(downlink_rate));\n        }\n\n        [[nodiscard]] QString DisplayTraffic() const {\n            if (downlink + uplink == 0) return \"\";\n            return UNICODE_LRO + QStringLiteral(\"%1↑ %2↓\").arg(ReadableSize(uplink), ReadableSize(downlink));\n        }\n    };\n} // namespace NekoGui_traffic\n"
  },
  {
    "path": "db/traffic/TrafficLooper.cpp",
    "content": "#include \"TrafficLooper.hpp\"\n\n#include \"rpc/gRPC.h\"\n#include \"ui/mainwindow_interface.h\"\n\n#include <QThread>\n#include <QJsonObject>\n#include <QJsonArray>\n#include <QJsonDocument>\n#include <QElapsedTimer>\n\nnamespace NekoGui_traffic {\n\n    TrafficLooper *trafficLooper = new TrafficLooper;\n    QElapsedTimer elapsedTimer;\n\n    TrafficData *TrafficLooper::update_stats(TrafficData *item) {\n#ifndef NKR_NO_GRPC\n        // last update\n        auto now = elapsedTimer.elapsed();\n        auto interval = now - item->last_update;\n        item->last_update = now;\n        if (interval <= 0) return nullptr;\n\n        // query\n        auto uplink = NekoGui_rpc::defaultClient->QueryStats(item->tag, \"uplink\");\n        auto downlink = NekoGui_rpc::defaultClient->QueryStats(item->tag, \"downlink\");\n\n        // add diff\n        item->downlink += downlink;\n        item->uplink += uplink;\n        item->downlink_rate = downlink * 1000 / interval;\n        item->uplink_rate = uplink * 1000 / interval;\n\n        // return diff\n        auto ret = new TrafficData(item->tag);\n        ret->downlink = downlink;\n        ret->uplink = uplink;\n        ret->downlink_rate = item->downlink_rate;\n        ret->uplink_rate = item->uplink_rate;\n        return ret;\n#endif\n        return nullptr;\n    }\n\n    QJsonArray TrafficLooper::get_connection_list() {\n#ifndef NKR_NO_GRPC\n        auto str = NekoGui_rpc::defaultClient->ListConnections();\n        QJsonDocument jsonDocument = QJsonDocument::fromJson(str.c_str());\n        return jsonDocument.array();\n#else\n        return QJsonArray{};\n#endif\n    }\n\n    void TrafficLooper::UpdateAll() {\n        std::map<std::string, TrafficData *> updated; // tag to diff\n        for (const auto &item: this->items) {\n            auto data = item.get();\n            auto diff = updated[data->tag];\n            // 避免重复查询一个 outbound tag\n            if (diff == nullptr) {\n                diff = update_stats(data);\n                updated[data->tag] = diff;\n            } else {\n                data->uplink += diff->uplink;\n                data->downlink += diff->downlink;\n                data->uplink_rate = diff->uplink_rate;\n                data->downlink_rate = diff->downlink_rate;\n            }\n        }\n        updated[bypass->tag] = update_stats(bypass);\n        //\n        for (const auto &pair: updated) {\n            delete pair.second;\n        }\n    }\n\n    void TrafficLooper::Loop() {\n        elapsedTimer.start();\n        while (true) {\n            auto sleep_ms = NekoGui::dataStore->traffic_loop_interval;\n            if (sleep_ms < 500 || sleep_ms > 5000) sleep_ms = 1000;\n            QThread::msleep(sleep_ms);\n            if (NekoGui::dataStore->traffic_loop_interval == 0) continue; // user disabled\n\n            // profile start and stop\n            if (!loop_enabled) {\n                // 停止\n                if (looping) {\n                    looping = false;\n                    runOnUiThread([=] {\n                        auto m = GetMainWindow();\n                        m->refresh_status(\"STOP\");\n                    });\n                }\n                continue;\n            } else {\n                // 开始\n                if (!looping) {\n                    looping = true;\n                }\n            }\n\n            // do update\n            loop_mutex.lock();\n\n            UpdateAll();\n\n            // do conn list update\n            QJsonArray conn_list;\n            if (NekoGui::dataStore->connection_statistics) {\n                conn_list = get_connection_list();\n            }\n\n            loop_mutex.unlock();\n\n            // post to UI\n            runOnUiThread([=] {\n                auto m = GetMainWindow();\n                if (proxy != nullptr) {\n                    m->refresh_status(QObject::tr(\"Proxy: %1\\nDirect: %2\").arg(proxy->DisplaySpeed(), bypass->DisplaySpeed()));\n                }\n                for (const auto &item: items) {\n                    if (item->id < 0) continue;\n                    m->refresh_proxy_list(item->id);\n                }\n                if (NekoGui::dataStore->connection_statistics) {\n                    m->refresh_connection_list(conn_list);\n                }\n            });\n        }\n    }\n\n} // namespace NekoGui_traffic\n"
  },
  {
    "path": "db/traffic/TrafficLooper.hpp",
    "content": "#pragma once\n\n#include <QString>\n#include <QList>\n#include <QMutex>\n\n#include \"TrafficData.hpp\"\n\nnamespace NekoGui_traffic {\n    class TrafficLooper {\n    public:\n        bool loop_enabled = false;\n        bool looping = false;\n        QMutex loop_mutex;\n\n        QList<std::shared_ptr<TrafficData>> items;\n        TrafficData *proxy = nullptr;\n\n        void UpdateAll();\n\n        void Loop();\n\n    private:\n        TrafficData *bypass = new TrafficData(\"bypass\");\n\n        [[nodiscard]] static TrafficData *update_stats(TrafficData *item);\n\n        [[nodiscard]] static QJsonArray get_connection_list();\n    };\n\n    extern TrafficLooper *trafficLooper;\n} // namespace NekoGui_traffic\n"
  },
  {
    "path": "docs/Build_Core.md",
    "content": "## 构建 nekobox_core\n\n### 目录结构\n\n```\n  | nekoray\n  |   go/cmd/*\n  | sing-box-extra\n  | sing-box\n  | ......\n```\n\n### 常规构建\n\n1. `bash libs/get_source.sh` （自动下载目录结构，自动 checkout commit）\n2. `GOOS=windows GOARCH=amd64 bash libs/build_go.sh`\n\n具体支持的 GOOS 和 GOARCH 请看 `libs/build_go.sh`\n\n非官方构建无需编译 `updater` `launcher`\n\n### sing-box tags\n\n具体使用的 tags 请看 `libs/build_go.sh`\n"
  },
  {
    "path": "docs/Build_Linux.md",
    "content": "在 Linux 下编译 Nekoray\n\n## git clone 源码\n\n```\ngit clone https://github.com/MatsuriDayo/nekoray.git --recursive\n```\n\n## 简单编译法\n\n条件：\n\n1. C++ 依赖：`protobuf yaml-cpp zxing-cpp` 已用包管理器安装，并符合版本要求。\n2. 已安装 `qtbase` `qtsvg` `qttools` `qtx11extras`\n3. 已安装 Qt `5.12.x` 或 `5.15.x`\n4. 系统为 `x86-64-linux-gnu`\n\n```shell\nmkdir build\ncd build\ncmake -GNinja ..\nninja\n```\n\n编译完成后得到 `nekobox`\n\n解压 Release 的压缩包，替换其中的 `nekobox`，删除 `launcher` 即可使用。\n\n## 复杂编译法\n\n### CMake 参数\n\n| CMake 参数          | 默认值               | 含义                    |\n|-------------------|-------------------|-----------------------|\n| QT_VERSION_MAJOR  | 5                 | QT版本                  |\n| NKR_NO_EXTERNAL   |                   | 不包含外部 C/C++ 依赖 (以下所有) |\n| NKR_NO_YAML       |                   | 不包含 yaml-cpp          |\n| NKR_NO_QHOTKEY    |                   | 不包含 qhotkey           |\n| NKR_NO_ZXING      |                   | 不包含 zxing             |\n| NKR_NO_GRPC       |                   | 不包含 gRPC              |\n| NKR_PACKAGE       |                   | 编译 package 版本 (aur)   |\n| NKR_LIBS          | ./libs/deps/built | 依赖搜索目录                |\n| NKR_DISABLE_LIBS  |                   | 禁用 NKR_LIBS           |\n\n1. `NKR_LIBS` 的值会被追加到 `CMAKE_PREFIX_PATH`\n2. `NKR_PACKAGE` 打开后，`NKR_LIBS` 的默认值为 `./libs/deps/package` ，具体依赖请看 `build_deps_all.sh`\n3. `NKR_PACKAGE` 打开后，应用将使用 appdata 目录存放配置，自动更新等功能将被禁用。\n\n### C++ 部分\n\n当您的发行版没有上面几个 C++ 依赖包，或者版本不符合要求时，可以参考 `build_deps_all.sh` 编译脚本自行编译。\n\n条件： 已安装 Qt `5.12.x` 或 `5.15.x`\n\n#### 编译安装 C/C++ 依赖\n\n（这一步可能要挂梯）\n\n```shell\n./libs/build_deps_all.sh\n```\n\n#### 编译本体\n\n```shell\nmkdir build\ncd build\ncmake -GNinja ..\nninja\n```\n\n编译完成后得到 `nekobox`\n\n### Go 部分编译\n\n请看 [Build_Core.md](./Build_Core.md)\n"
  },
  {
    "path": "docs/Build_Windows.md",
    "content": "在 Windows 下编译 Nekoray\n\n### git clone 源码\n\n```\ngit clone https://github.com/MatsuriDayo/nekoray.git --recursive\n```\n\n### 安装 Visual Studio\n\n从微软官网安装，可以使用 2019 和 2022 版本，安装 Win32 C++ 开发环境。\n\n安装好后可以在「开始」菜单找到 `x64 Native Tools Command Prompt`\n\n本文之后的命令均在该 cmd 内执行。`cmake` `ninja` 等工具使用 VS 自带的即可。\n\n### 下载 Qt SDK\n\n目前 Windows Release 使用的版本是 Qt 6.5.x\n\n下载解压后，将 bin 目录添加到环境变量。\n\n#### Release 编译用到的 Qt 包下载 (MSVC2019 x86_64)\n\nhttps://github.com/MatsuriDayo/nekoray_qt_runtime/releases/download/20220503/Qt6.5.0-Windows-x86_64-VS2022-17.5.5-20230507.7z\n\n#### 官方签名版 Qt 5.15.2 （可选，已知有内存泄漏的BUG）\n\n在此下载 `qtbase` `qtsvg` `qttools` 的包并解压到同一个目录。\n\nhttps://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt5_5152/qt.qt5.5152.win64_msvc2019_64/\n\n### C++ 部分编译\n\n#### 编译安装 C/C++ 依赖\n\n（这一步可能要挂梯）\n\n```shell\nbash ./libs/build_deps_all.sh\n```\n\n目前只有 bash 脚本，没有批处理或 powershell，如果 Windows 没有带 bash 建议自行安装。\n\nCMake 参数等细节与 Linux 大同小异，有问题可以参照 Build_Linux 文档。\n\n#### 编译本体\n\n请根据你的 QT Sdk 的位置替换命令\n\n```shell\nmkdir build\ncd build\ncmake -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=D:/path/to/qt/5.15.2/msvc2019_64 ..\nninja\n```\n\n编译完成后得到 `nekobox.exe`\n\n最后运行 `windeployqt nekobox.exe` 自动复制所需 DLL 等文件到当前目录\n\n### Go 部分编译\n\n请看 [Build_Core.md](./Build_Core.md)\n"
  },
  {
    "path": "docs/RunFlags.md",
    "content": "# 运行参数\n\n- `-many` 无视同目录正在运行的实例，强行开启新的实例。\n- `-appdata` 开启后配置文件会指定目录，未指定目录则使用共享目录，无法多开和自动升级。\n- `-flag_reorder` 进行重新整理配置文件的顺序，并删除损坏和孤立的配置。\n"
  },
  {
    "path": "docs/Run_Linux.md",
    "content": "## Linux 安装\n\n### Debian 系发行版\n\n使用 Debian 系发行版时，推荐使用 .deb 包安装：\n\n```shell\nsudo apt install ./nekoray-*-debian-x64.deb\n```\n\n安装完成后，桌面快捷方式启动自带参数 `-appdata`，如果想要直接启动并使用之前的配置，注意附带本参数。\n\n### Arch 系发行版\n\n使用 Arch 系发行版时，推荐从 ```aur``` 或 ```archlinuxcn``` 安装：\n\n#### AUR\n##### 最新稳定版\n\n```shell\n[yay/paru] -S nekoray\n```\n\n##### 最新 Git 版 (开发版)\n\n```shell\n[yay/paru] -S nekoray-git\n```\n\n#### archlinuxcn\n\n##### 最新稳定版\n\n```shell\nsudo pacman -S nekoray\n```\n\n##### 最新 Git 版 (开发版)\n\n```shell\nsudo pacman -S nekoray-git\n```\n\n### 其他发行版\n\n下载 .zip 文件，解压到合适的路径，开箱即用。\n\n或下载 .AppImage，并使用 `chmod +x nekoray-*-AppImage-x64.AppImage` 给予可执行权限。\n\n具体使用方法见下文。\n\n## Linux 运行\n\n**使用 Linux 系统相信您已具备基本的排错能力，\n本项目不提供特定发行版/架构的支持，预编译文件不能满足您的需求时，请自行编译/适配。**\n\n已知部分 Linux 发行版无法使用、非 x86_64 暂无适配，可以尝试自行编译。\n\n目前 Release 便携包解压后，有两种使用方法：\n\n1. System: 若要使用系统的 Qt5 运行库，请执行 `./nekoray`\n2. Bundle: 若要使用预编译的 Qt 运行库，请执行 `./launcher`\n\n### Bundle\n\n要求：已安装主流的发行版和 xcb 桌面环境。\n\n运行： `./launcher` 或 部分系统可双击打开\n\nlauncher 参数\n\n* `./launcher -- -appdata` ( `--` 后的参数传递给主程序 )\n* `-debug` Debug mode\n\nUbuntu 22.04: `sudo apt install libxcb-xinerama0`\n\n### System\n\n要求：已安装主流的发行版和 xcb 桌面环境，已安装 Qt5.12 ~ Qt5.15 环境。\n\n运行： `./nekoray` 或 部分系统可双击打开。如果无法运行，建议使用 Bundle 版。\n"
  },
  {
    "path": "docs/readme.md",
    "content": "# 技术文档\n\n# Technical documentation\n\n1. Build GUI: `Build_*.md`\n2. Build Core: `Build_Core.md`\n"
  },
  {
    "path": "fmt/AbstractBean.cpp",
    "content": "#include \"includes.h\"\n\n#include <QApplication>\n#include <QHostInfo>\n#include <QUrl>\n\nnamespace NekoGui_fmt {\n    AbstractBean::AbstractBean(int version) {\n        this->version = version;\n        _add(new configItem(\"_v\", &this->version, itemType::integer));\n        _add(new configItem(\"name\", &name, itemType::string));\n        _add(new configItem(\"addr\", &serverAddress, itemType::string));\n        _add(new configItem(\"port\", &serverPort, itemType::integer));\n        _add(new configItem(\"c_cfg\", &custom_config, itemType::string));\n        _add(new configItem(\"c_out\", &custom_outbound, itemType::string));\n    }\n\n    QString AbstractBean::ToNekorayShareLink(const QString &type) {\n        auto b = ToJson();\n        QUrl url;\n        url.setScheme(\"nekoray\");\n        url.setHost(type);\n        url.setFragment(QJsonObject2QString(b, true)\n                            .toUtf8()\n                            .toBase64(QByteArray::Base64UrlEncoding));\n        return url.toString();\n    }\n\n    QString AbstractBean::DisplayAddress() {\n        return ::DisplayAddress(serverAddress, serverPort);\n    }\n\n    QString AbstractBean::DisplayName() {\n        if (name.isEmpty()) {\n            return DisplayAddress();\n        }\n        return name;\n    }\n\n    QString AbstractBean::DisplayTypeAndName() {\n        return QStringLiteral(\"[%1] %2\").arg(DisplayType(), DisplayName());\n    }\n\n    void AbstractBean::ResolveDomainToIP(const std::function<void()> &onFinished) {\n        bool noResolve = false;\n        if (dynamic_cast<ChainBean *>(this) != nullptr) noResolve = true;\n        if (dynamic_cast<CustomBean *>(this) != nullptr) noResolve = true;\n        if (dynamic_cast<NaiveBean *>(this) != nullptr) noResolve = true;\n        if (IsIpAddress(serverAddress)) noResolve = true;\n        if (noResolve) {\n            onFinished();\n            return;\n        }\n\n#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) // TODO older QT\n        QHostInfo::lookupHost(serverAddress, QApplication::instance(), [=](const QHostInfo &host) {\n            auto addr = host.addresses();\n            if (!addr.isEmpty()) {\n                auto domain = serverAddress;\n                auto stream = GetStreamSettings(this);\n\n                // replace serverAddress\n                serverAddress = addr.first().toString();\n\n                // replace ws tls\n                if (stream != nullptr) {\n                    if (stream->security == \"tls\" && stream->sni.isEmpty()) {\n                        stream->sni = domain;\n                    }\n                    if (stream->network == \"ws\" && stream->host.isEmpty()) {\n                        stream->host = domain;\n                    }\n                }\n            }\n            onFinished();\n        });\n#endif\n    }\n} // namespace NekoGui_fmt\n"
  },
  {
    "path": "fmt/AbstractBean.hpp",
    "content": "#pragma once\n\n#include <QJsonObject>\n#include <QJsonArray>\n\n#include \"main/NekoGui.hpp\"\n\nnamespace NekoGui_fmt {\n    struct CoreObjOutboundBuildResult {\n    public:\n        QJsonObject outbound;\n        QString error;\n    };\n\n    struct ExternalBuildResult {\n    public:\n        QString program;\n        QStringList env;\n        QStringList arguments;\n        //\n        QString tag;\n        //\n        QString error;\n        QString config_export;\n    };\n\n    class AbstractBean : public JsonStore {\n    public:\n        int version;\n\n        QString name = \"\";\n        QString serverAddress = \"127.0.0.1\";\n        int serverPort = 1080;\n\n        QString custom_config = \"\";\n        QString custom_outbound = \"\";\n\n        explicit AbstractBean(int version);\n\n        //\n\n        QString ToNekorayShareLink(const QString &type);\n\n        void ResolveDomainToIP(const std::function<void()> &onFinished);\n\n        //\n\n        [[nodiscard]] virtual QString DisplayAddress();\n\n        [[nodiscard]] virtual QString DisplayName();\n\n        virtual QString DisplayCoreType() { return software_core_name; };\n\n        virtual QString DisplayType() { return {}; };\n\n        virtual QString DisplayTypeAndName();\n\n        //\n\n        virtual int NeedExternal(bool isFirstProfile) { return 0; };\n\n        virtual CoreObjOutboundBuildResult BuildCoreObjSingBox() { return {}; };\n\n        virtual ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) { return {}; };\n\n        virtual QString ToShareLink() { return {}; };\n    };\n\n} // namespace NekoGui_fmt\n"
  },
  {
    "path": "fmt/Bean2CoreObj_box.cpp",
    "content": "#include \"db/ProxyEntity.hpp\"\n#include \"fmt/includes.h\"\n\nnamespace NekoGui_fmt {\n    void V2rayStreamSettings::BuildStreamSettingsSingBox(QJsonObject *outbound) {\n        // https://sing-box.sagernet.org/configuration/shared/v2ray-transport\n\n        if (network != \"tcp\") {\n            QJsonObject transport{{\"type\", network}};\n            if (network == \"ws\") {\n                if (!host.isEmpty()) transport[\"headers\"] = QJsonObject{{\"Host\", host}};\n                // ws path & ed\n                auto pathWithoutEd = SubStrBefore(path, \"?ed=\");\n                if (!pathWithoutEd.isEmpty()) transport[\"path\"] = pathWithoutEd;\n                if (pathWithoutEd != path) {\n                    auto ed = SubStrAfter(path, \"?ed=\").toInt();\n                    if (ed > 0) {\n                        transport[\"max_early_data\"] = ed;\n                        transport[\"early_data_header_name\"] = \"Sec-WebSocket-Protocol\";\n                    }\n                }\n                if (ws_early_data_length > 0) {\n                    transport[\"max_early_data\"] = ws_early_data_length;\n                    transport[\"early_data_header_name\"] = ws_early_data_name;\n                }\n            } else if (network == \"http\") {\n                if (!path.isEmpty()) transport[\"path\"] = path;\n                if (!host.isEmpty()) transport[\"host\"] = QList2QJsonArray(host.split(\",\"));\n            } else if (network == \"grpc\") {\n                if (!path.isEmpty()) transport[\"service_name\"] = path;\n            } else if (network == \"httpupgrade\") {\n                if (!path.isEmpty()) transport[\"path\"] = path;\n                if (!host.isEmpty()) transport[\"host\"] = host;\n            }\n            outbound->insert(\"transport\", transport);\n        } else if (header_type == \"http\") {\n            // TCP + headerType\n            QJsonObject transport{\n                {\"type\", \"http\"},\n                {\"method\", \"GET\"},\n                {\"path\", path},\n                {\"headers\", QJsonObject{{\"Host\", QList2QJsonArray(host.split(\",\"))}}},\n            };\n            outbound->insert(\"transport\", transport);\n        }\n\n        // 对应字段 tls\n        if (security == \"tls\") {\n            QJsonObject tls{{\"enabled\", true}};\n            if (allow_insecure || NekoGui::dataStore->skip_cert) tls[\"insecure\"] = true;\n            if (!sni.trimmed().isEmpty()) tls[\"server_name\"] = sni;\n            if (!certificate.trimmed().isEmpty()) {\n                tls[\"certificate\"] = certificate.trimmed();\n            }\n            if (!alpn.trimmed().isEmpty()) {\n                tls[\"alpn\"] = QList2QJsonArray(alpn.split(\",\"));\n            }\n            QString fp = utlsFingerprint;\n            if (!reality_pbk.trimmed().isEmpty()) {\n                tls[\"reality\"] = QJsonObject{\n                    {\"enabled\", true},\n                    {\"public_key\", reality_pbk},\n                    {\"short_id\", reality_sid.split(\",\")[0]},\n                };\n                if (fp.isEmpty()) fp = \"random\";\n            }\n            if (!fp.isEmpty()) {\n                tls[\"utls\"] = QJsonObject{\n                    {\"enabled\", true},\n                    {\"fingerprint\", fp},\n                };\n            }\n            outbound->insert(\"tls\", tls);\n        }\n\n        if (outbound->value(\"type\").toString() == \"vmess\" || outbound->value(\"type\").toString() == \"vless\") {\n            outbound->insert(\"packet_encoding\", packet_encoding);\n        }\n    }\n\n    CoreObjOutboundBuildResult SocksHttpBean::BuildCoreObjSingBox() {\n        CoreObjOutboundBuildResult result;\n\n        QJsonObject outbound;\n        outbound[\"type\"] = socks_http_type == type_HTTP ? \"http\" : \"socks\";\n        if (socks_http_type == type_Socks4) outbound[\"version\"] = \"4\";\n        outbound[\"server\"] = serverAddress;\n        outbound[\"server_port\"] = serverPort;\n\n        if (!username.isEmpty() && !password.isEmpty()) {\n            outbound[\"username\"] = username;\n            outbound[\"password\"] = password;\n        }\n\n        stream->BuildStreamSettingsSingBox(&outbound);\n        result.outbound = outbound;\n        return result;\n    }\n\n    CoreObjOutboundBuildResult ShadowSocksBean::BuildCoreObjSingBox() {\n        CoreObjOutboundBuildResult result;\n\n        QJsonObject outbound{{\"type\", \"shadowsocks\"}};\n        outbound[\"server\"] = serverAddress;\n        outbound[\"server_port\"] = serverPort;\n        outbound[\"method\"] = method;\n        outbound[\"password\"] = password;\n\n        if (uot != 0) {\n            QJsonObject udp_over_tcp{\n                {\"enabled\", true},\n                {\"version\", uot},\n            };\n            outbound[\"udp_over_tcp\"] = udp_over_tcp;\n        } else {\n            outbound[\"udp_over_tcp\"] = false;\n        }\n\n        if (!plugin.trimmed().isEmpty()) {\n            outbound[\"plugin\"] = SubStrBefore(plugin, \";\");\n            outbound[\"plugin_opts\"] = SubStrAfter(plugin, \";\");\n        }\n\n        stream->BuildStreamSettingsSingBox(&outbound);\n        result.outbound = outbound;\n        return result;\n    }\n\n    CoreObjOutboundBuildResult VMessBean::BuildCoreObjSingBox() {\n        CoreObjOutboundBuildResult result;\n\n        QJsonObject outbound{\n            {\"type\", \"vmess\"},\n            {\"server\", serverAddress},\n            {\"server_port\", serverPort},\n            {\"uuid\", uuid.trimmed()},\n            {\"alter_id\", aid},\n            {\"security\", security},\n        };\n\n        stream->BuildStreamSettingsSingBox(&outbound);\n        result.outbound = outbound;\n        return result;\n    }\n\n    CoreObjOutboundBuildResult TrojanVLESSBean::BuildCoreObjSingBox() {\n        CoreObjOutboundBuildResult result;\n\n        QJsonObject outbound{\n            {\"type\", proxy_type == proxy_VLESS ? \"vless\" : \"trojan\"},\n            {\"server\", serverAddress},\n            {\"server_port\", serverPort},\n        };\n\n        QJsonObject settings;\n        if (proxy_type == proxy_VLESS) {\n            if (flow.right(7) == \"-udp443\") {\n                // 检查末尾是否包含\"-udp443\"，如果是，则删去\n                flow.chop(7);\n            } else if (flow == \"none\") {\n                // 不使用 flow\n                flow = \"\";\n            }\n            outbound[\"uuid\"] = password.trimmed();\n            outbound[\"flow\"] = flow;\n        } else {\n            outbound[\"password\"] = password;\n        }\n\n        stream->BuildStreamSettingsSingBox(&outbound);\n        result.outbound = outbound;\n        return result;\n    }\n\n    CoreObjOutboundBuildResult QUICBean::BuildCoreObjSingBox() {\n        CoreObjOutboundBuildResult result;\n\n        QJsonObject coreTlsObj{\n            {\"enabled\", true},\n            {\"disable_sni\", disableSni},\n            {\"insecure\", allowInsecure},\n            {\"certificate\", caText.trimmed()},\n            {\"server_name\", sni},\n        };\n        if (!alpn.trimmed().isEmpty()) coreTlsObj[\"alpn\"] = QList2QJsonArray(alpn.split(\",\"));\n        if (proxy_type == proxy_Hysteria2) coreTlsObj[\"alpn\"] = \"h3\";\n\n        QJsonObject outbound{\n            {\"server\", serverAddress},\n            {\"server_port\", serverPort},\n            {\"tls\", coreTlsObj},\n        };\n\n        if (proxy_type == proxy_Hysteria2) {\n            outbound[\"type\"] = \"hysteria2\";\n            outbound[\"password\"] = password;\n            outbound[\"up_mbps\"] = uploadMbps;\n            outbound[\"down_mbps\"] = downloadMbps;\n\n            if (!hopPort.trimmed().isEmpty()) {\n                outbound[\"hop_ports\"] = hopPort;\n                outbound[\"hop_interval\"] = hopInterval;\n            }\n            if (!obfsPassword.isEmpty()) {\n                outbound[\"obfs\"] = QJsonObject{\n                    {\"type\", \"salamander\"},\n                    {\"password\", obfsPassword},\n                };\n            }\n        } else if (proxy_type == proxy_TUIC) {\n            outbound[\"type\"] = \"tuic\";\n            outbound[\"uuid\"] = uuid;\n            outbound[\"password\"] = password;\n            outbound[\"congestion_control\"] = congestionControl;\n            if (uos) {\n                outbound[\"udp_over_stream\"] = true;\n            } else {\n                outbound[\"udp_relay_mode\"] = udpRelayMode;\n            }\n            outbound[\"zero_rtt_handshake\"] = zeroRttHandshake;\n            if (!heartbeat.trimmed().isEmpty()) outbound[\"heartbeat\"] = heartbeat;\n        }\n\n        result.outbound = outbound;\n        return result;\n    }\n\n    CoreObjOutboundBuildResult CustomBean::BuildCoreObjSingBox() {\n        CoreObjOutboundBuildResult result;\n\n        if (core == \"internal\") {\n            result.outbound = QString2QJsonObject(config_simple);\n        }\n\n        return result;\n    }\n} // namespace NekoGui_fmt\n"
  },
  {
    "path": "fmt/Bean2External.cpp",
    "content": "#include \"db/ProxyEntity.hpp\"\n#include \"fmt/includes.h\"\n\n#include <QFile>\n#include <QDir>\n#include <QFileInfo>\n#include <QUrl>\n\n#define WriteTempFile(fn, data)                                   \\\n    QDir dir;                                                     \\\n    if (!dir.exists(\"temp\")) dir.mkdir(\"temp\");                   \\\n    QFile f(QStringLiteral(\"temp/\") + fn);                               \\\n    bool ok = f.open(QIODevice::WriteOnly | QIODevice::Truncate); \\\n    if (ok) {                                                     \\\n        f.write(data);                                            \\\n    } else {                                                      \\\n        result.error = f.errorString();                           \\\n    }                                                             \\\n    f.close();                                                    \\\n    auto TempFile = QFileInfo(f).absoluteFilePath();\n\nnamespace NekoGui_fmt {\n    // -1: Cannot use this config\n    // 0: Internal\n    // 1: Mapping External\n    // 2: Direct External\n\n    int NaiveBean::NeedExternal(bool isFirstProfile) {\n        if (isFirstProfile) {\n            if (NekoGui::dataStore->spmode_vpn) {\n                return 1;\n            }\n            return 2;\n        }\n        return 1;\n    }\n\n    int QUICBean::NeedExternal(bool isFirstProfile) {\n        auto extCore = [=] {\n            if (isFirstProfile) {\n                if (NekoGui::dataStore->spmode_vpn && hopPort.trimmed().isEmpty()) {\n                    return 1;\n                }\n                return 2;\n            } else {\n                if (!hopPort.trimmed().isEmpty()) {\n                    return -1;\n                }\n            }\n            return 1;\n        };\n\n        if (!forceExternal) {\n            // sing-box support\n            return 0;\n        } else {\n            // external core support\n            return extCore();\n        }\n    }\n\n    int CustomBean::NeedExternal(bool isFirstProfile) {\n        if (core == \"internal\" || core == \"internal-full\") return 0;\n        return 1;\n    }\n\n    ExternalBuildResult NaiveBean::BuildExternal(int mapping_port, int socks_port, int external_stat) {\n        ExternalBuildResult result{NekoGui::dataStore->extraCore->Get(\"naive\")};\n\n        auto is_direct = external_stat == 2;\n        auto domain_address = sni.isEmpty() ? serverAddress : sni;\n        auto connect_address = is_direct ? serverAddress : \"127.0.0.1\";\n        auto connect_port = is_direct ? serverPort : mapping_port;\n        domain_address = WrapIPV6Host(domain_address);\n        connect_address = WrapIPV6Host(connect_address);\n\n        auto proxy_url = QUrl();\n        proxy_url.setScheme(protocol);\n        proxy_url.setUserName(username);\n        proxy_url.setPassword(password);\n        proxy_url.setPort(connect_port);\n        proxy_url.setHost(domain_address);\n\n        if (!disable_log) result.arguments += \"--log\";\n        result.arguments += \"--listen=socks://127.0.0.1:\" + Int2String(socks_port);\n        result.arguments += \"--proxy=\" + proxy_url.toString(QUrl::FullyEncoded);\n        if (domain_address != connect_address)\n            result.arguments += \"--host-resolver-rules=MAP \" + domain_address + \" \" + connect_address;\n        if (insecure_concurrency > 0) result.arguments += \"--insecure-concurrency=\" + Int2String(insecure_concurrency);\n        if (!extra_headers.trimmed().isEmpty()) result.arguments += \"--extra-headers=\" + extra_headers;\n        if (!certificate.trimmed().isEmpty()) {\n            WriteTempFile(\"naive_\" + GetRandomString(10) + \".crt\", certificate.toUtf8());\n            result.env += \"SSL_CERT_FILE=\" + TempFile;\n        }\n\n        auto config_export = QStringList{result.program};\n        config_export += result.arguments;\n        result.config_export = QStringList2Command(config_export);\n\n        return result;\n    }\n\n    ExternalBuildResult QUICBean::BuildExternal(int mapping_port, int socks_port, int external_stat) {\n        if (proxy_type == proxy_TUIC) {\n            ExternalBuildResult result{NekoGui::dataStore->extraCore->Get(\"tuic\")};\n\n            QJsonObject relay;\n\n            relay[\"uuid\"] = uuid;\n            relay[\"password\"] = password;\n            relay[\"udp_relay_mode\"] = udpRelayMode;\n            relay[\"congestion_control\"] = congestionControl;\n            relay[\"zero_rtt_handshake\"] = zeroRttHandshake;\n            relay[\"disable_sni\"] = disableSni;\n            if (!heartbeat.trimmed().isEmpty()) relay[\"heartbeat\"] = heartbeat;\n            if (!alpn.trimmed().isEmpty()) relay[\"alpn\"] = QList2QJsonArray(alpn.split(\",\"));\n\n            if (!caText.trimmed().isEmpty()) {\n                WriteTempFile(\"tuic_\" + GetRandomString(10) + \".crt\", caText.toUtf8());\n                QJsonArray certificate;\n                certificate.append(TempFile);\n                relay[\"certificates\"] = certificate;\n            }\n\n            // The most confused part of TUIC......\n            if (serverAddress == sni) {\n                relay[\"server\"] = serverAddress + \":\" + Int2String(serverPort);\n            } else {\n                relay[\"server\"] = sni + \":\" + Int2String(serverPort);\n                relay[\"ip\"] = serverAddress;\n            }\n\n            QJsonObject local{\n                {\"server\", \"127.0.0.1:\" + Int2String(socks_port)},\n            };\n\n            QJsonObject config{\n                {\"relay\", relay},\n                {\"local\", local},\n            };\n\n            //\n\n            result.config_export = QJsonObject2QString(config, false);\n            WriteTempFile(\"tuic_\" + GetRandomString(10) + \".json\", result.config_export.toUtf8());\n            result.arguments = QStringList{\"-c\", TempFile};\n\n            return result;\n        } else if (proxy_type == proxy_Hysteria2) {\n            ExternalBuildResult result{NekoGui::dataStore->extraCore->Get(\"hysteria2\")};\n\n            QJsonObject config;\n\n            auto server = serverAddress;\n            if (!hopPort.trimmed().isEmpty()) {\n                server = WrapIPV6Host(server) + \":\" + hopPort;\n            } else {\n                server = WrapIPV6Host(server) + \":\" + Int2String(serverPort);\n            }\n\n            QJsonObject transport;\n            transport[\"type\"] = \"udp\";\n            transport[\"udp\"] = QJsonObject{\n                {\"hopInterval\", QString::number(hopInterval) + \"s\"},\n            };\n            config[\"transport\"] = transport;\n\n            config[\"server\"] = server;\n            config[\"socks5\"] = QJsonObject{\n                {\"listen\", \"127.0.0.1:\" + Int2String(socks_port)},\n                {\"disableUDP\", false},\n            };\n            config[\"auth\"] = password;\n\n            QJsonObject bandwidth;\n            if (uploadMbps > 0) bandwidth[\"up\"] = Int2String(uploadMbps) + \" mbps\";\n            if (downloadMbps > 0) bandwidth[\"down\"] = Int2String(downloadMbps) + \" mbps\";\n            config[\"bandwidth\"] = bandwidth;\n\n            QJsonObject quic;\n            if (streamReceiveWindow > 0) quic[\"initStreamReceiveWindow\"] = streamReceiveWindow;\n            if (connectionReceiveWindow > 0) quic[\"initConnReceiveWindow\"] = connectionReceiveWindow;\n            if (disableMtuDiscovery) quic[\"disablePathMTUDiscovery\"] = true;\n            config[\"quic\"] = quic;\n\n            config[\"fastOpen\"] = true;\n            config[\"lazy\"] = true;\n\n            if (!obfsPassword.isEmpty()) {\n                QJsonObject obfs;\n                obfs[\"type\"] = \"salamander\";\n                obfs[\"salamander\"] = QJsonObject{\n                    {\"password\", obfsPassword},\n                };\n\n                config[\"obfs\"] = obfs;\n            }\n\n            QJsonObject tls;\n            auto sniGen = sni;\n            if (sni.isEmpty() && !IsIpAddress(serverAddress)) sniGen = serverAddress;\n            tls[\"sni\"] = sniGen;\n            if (allowInsecure) tls[\"insecure\"] = true;\n            if (!caText.trimmed().isEmpty()) {\n                WriteTempFile(\"hysteria2_\" + GetRandomString(10) + \".crt\", caText.toUtf8());\n                QJsonArray certificate;\n                certificate.append(TempFile);\n                tls[\"certificates\"] = certificate;\n            }\n            config[\"tls\"] = tls;\n\n            result.config_export = QJsonObject2QString(config, false);\n            WriteTempFile(\"hysteria2_\" + GetRandomString(10) + \".json\", result.config_export.toUtf8());\n            result.arguments = QStringList{\"-c\", TempFile};\n\n            return result;\n        }\n        ExternalBuildResult e;\n        e.error = \"unknown type\";\n        return e;\n    }\n\n    ExternalBuildResult CustomBean::BuildExternal(int mapping_port, int socks_port, int external_stat) {\n        ExternalBuildResult result{NekoGui::dataStore->extraCore->Get(core)};\n\n        result.arguments = command; // TODO split?\n\n        for (int i = 0; i < result.arguments.length(); i++) {\n            auto arg = result.arguments[i];\n            arg = arg.replace(\"%mapping_port%\", Int2String(mapping_port));\n            arg = arg.replace(\"%socks_port%\", Int2String(socks_port));\n            arg = arg.replace(\"%server_addr%\", serverAddress);\n            arg = arg.replace(\"%server_port%\", Int2String(serverPort));\n            result.arguments[i] = arg;\n        }\n\n        if (!config_simple.trimmed().isEmpty()) {\n            auto config = config_simple;\n            config = config.replace(\"%mapping_port%\", Int2String(mapping_port));\n            config = config.replace(\"%socks_port%\", Int2String(socks_port));\n            config = config.replace(\"%server_addr%\", serverAddress);\n            config = config.replace(\"%server_port%\", Int2String(serverPort));\n\n            // suffix\n            QString suffix;\n            if (!config_suffix.isEmpty()) {\n                suffix = \".\" + config_suffix;\n            } else if (!QString2QJsonObject(config).isEmpty()) {\n                // trojan-go: unsupported config format: xxx.tmp. use .yaml or .json instead.\n                suffix = \".json\";\n            }\n\n            // write config\n            WriteTempFile(\"custom_\" + GetRandomString(10) + suffix, config.toUtf8());\n            for (int i = 0; i < result.arguments.count(); i++) {\n                result.arguments[i] = result.arguments[i].replace(\"%config%\", TempFile);\n            }\n\n            result.config_export = config;\n        }\n\n        return result;\n    }\n\n} // namespace NekoGui_fmt\n"
  },
  {
    "path": "fmt/Bean2Link.cpp",
    "content": "#include \"db/ProxyEntity.hpp\"\n#include \"fmt/includes.h\"\n\n#include <QUrlQuery>\n\nnamespace NekoGui_fmt {\n    QString SocksHttpBean::ToShareLink() {\n        QUrl url;\n        if (socks_http_type == type_HTTP) { // http\n            if (stream->security == \"tls\") {\n                url.setScheme(\"https\");\n            } else {\n                url.setScheme(\"http\");\n            }\n        } else {\n            url.setScheme(QStringLiteral(\"socks%1\").arg(socks_http_type));\n        }\n        if (!name.isEmpty()) url.setFragment(name);\n        if (!username.isEmpty()) url.setUserName(username);\n        if (!password.isEmpty()) url.setPassword(password);\n        url.setHost(serverAddress);\n        url.setPort(serverPort);\n        return url.toString(QUrl::FullyEncoded);\n    }\n\n    QString TrojanVLESSBean::ToShareLink() {\n        QUrl url;\n        QUrlQuery query;\n        url.setScheme(proxy_type == proxy_VLESS ? \"vless\" : \"trojan\");\n        url.setUserName(password);\n        url.setHost(serverAddress);\n        url.setPort(serverPort);\n        if (!name.isEmpty()) url.setFragment(name);\n\n        //  security\n        auto security = stream->security;\n        if (security == \"tls\" && !stream->reality_pbk.trimmed().isEmpty()) security = \"reality\";\n        query.addQueryItem(\"security\", security);\n\n        if (!stream->sni.isEmpty()) query.addQueryItem(\"sni\", stream->sni);\n        if (!stream->alpn.isEmpty()) query.addQueryItem(\"alpn\", stream->alpn);\n        if (stream->allow_insecure) query.addQueryItem(\"allowInsecure\", \"1\");\n        if (!stream->utlsFingerprint.isEmpty()) query.addQueryItem(\"fp\", stream->utlsFingerprint);\n\n        if (security == \"reality\") {\n            query.addQueryItem(\"pbk\", stream->reality_pbk);\n            if (!stream->reality_sid.isEmpty()) query.addQueryItem(\"sid\", stream->reality_sid);\n            if (!stream->reality_spx.isEmpty()) query.addQueryItem(\"spx\", stream->reality_spx);\n        }\n\n        // type\n        query.addQueryItem(\"type\", stream->network);\n\n        if (stream->network == \"ws\" || stream->network == \"http\" || stream->network == \"httpupgrade\") {\n            if (!stream->path.isEmpty()) query.addQueryItem(\"path\", stream->path);\n            if (!stream->host.isEmpty()) query.addQueryItem(\"host\", stream->host);\n        } else if (stream->network == \"grpc\") {\n            if (!stream->path.isEmpty()) query.addQueryItem(\"serviceName\", stream->path);\n        } else if (stream->network == \"tcp\") {\n            if (stream->header_type == \"http\") {\n                if (!stream->path.isEmpty()) query.addQueryItem(\"path\", stream->path);\n                query.addQueryItem(\"headerType\", \"http\");\n                query.addQueryItem(\"host\", stream->host);\n            }\n        }\n\n        // protocol\n        if (proxy_type == proxy_VLESS) {\n            if (!flow.isEmpty()) {\n                query.addQueryItem(\"flow\", flow);\n            }\n            query.addQueryItem(\"encryption\", \"none\");\n        }\n\n        url.setQuery(query);\n        return url.toString(QUrl::FullyEncoded);\n    }\n\n    const char* fixShadowsocksUserNameEncodeMagic = \"fixShadowsocksUserNameEncodeMagic-holder-for-QUrl\";\n\n    QString ShadowSocksBean::ToShareLink() {\n        QUrl url;\n        url.setScheme(\"ss\");\n        if (method.startsWith(\"2022-\")) {\n            url.setUserName(fixShadowsocksUserNameEncodeMagic);\n        } else {\n            auto method_password = method + \":\" + password;\n            url.setUserName(method_password.toUtf8().toBase64(QByteArray::Base64Option::Base64UrlEncoding));\n        }\n        url.setHost(serverAddress);\n        url.setPort(serverPort);\n        if (!name.isEmpty()) url.setFragment(name);\n        QUrlQuery q;\n        if (!plugin.isEmpty()) q.addQueryItem(\"plugin\", plugin);\n        if (!q.isEmpty()) url.setQuery(q);\n        //\n        auto link = url.toString(QUrl::FullyEncoded);\n        link = link.replace(fixShadowsocksUserNameEncodeMagic, method + \":\" + QUrl::toPercentEncoding(password));\n        return link;\n    }\n\n    QString VMessBean::ToShareLink() {\n        if (NekoGui::dataStore->old_share_link_format) {\n            // v2rayN format\n            QJsonObject N{\n                {\"v\", \"2\"},\n                {\"ps\", name},\n                {\"add\", serverAddress},\n                {\"port\", Int2String(serverPort)},\n                {\"id\", uuid},\n                {\"aid\", Int2String(aid)},\n                {\"net\", stream->network},\n                {\"host\", stream->host},\n                {\"path\", stream->path},\n                {\"type\", stream->header_type},\n                {\"scy\", security},\n                {\"tls\", stream->security == \"tls\" ? \"tls\" : \"\"},\n                {\"sni\", stream->sni},\n            };\n            return \"vmess://\" + QJsonObject2QString(N, true).toUtf8().toBase64();\n        } else {\n            // ducksoft format\n            QUrl url;\n            QUrlQuery query;\n            url.setScheme(\"vmess\");\n            url.setUserName(uuid);\n            url.setHost(serverAddress);\n            url.setPort(serverPort);\n            if (!name.isEmpty()) url.setFragment(name);\n\n            query.addQueryItem(\"encryption\", security);\n\n            //  security\n            auto security = stream->security;\n            if (security == \"tls\" && !stream->reality_pbk.trimmed().isEmpty()) security = \"reality\";\n            query.addQueryItem(\"security\", security);\n\n            if (!stream->sni.isEmpty()) query.addQueryItem(\"sni\", stream->sni);\n            if (stream->allow_insecure) query.addQueryItem(\"allowInsecure\", \"1\");\n            if (stream->utlsFingerprint.isEmpty()) {\n                query.addQueryItem(\"fp\", NekoGui::dataStore->utlsFingerprint);\n            } else {\n                query.addQueryItem(\"fp\", stream->utlsFingerprint);\n            }\n\n            if (security == \"reality\") {\n                query.addQueryItem(\"pbk\", stream->reality_pbk);\n                if (!stream->reality_sid.isEmpty()) query.addQueryItem(\"sid\", stream->reality_sid);\n                if (!stream->reality_spx.isEmpty()) query.addQueryItem(\"spx\", stream->reality_spx);\n            }\n\n            // type\n            query.addQueryItem(\"type\", stream->network);\n\n            if (stream->network == \"ws\" || stream->network == \"http\" || stream->network == \"httpupgrade\") {\n                if (!stream->path.isEmpty()) query.addQueryItem(\"path\", stream->path);\n                if (!stream->host.isEmpty()) query.addQueryItem(\"host\", stream->host);\n            } else if (stream->network == \"grpc\") {\n                if (!stream->path.isEmpty()) query.addQueryItem(\"serviceName\", stream->path);\n            } else if (stream->network == \"tcp\") {\n                if (stream->header_type == \"http\") {\n                    query.addQueryItem(\"headerType\", \"http\");\n                    query.addQueryItem(\"host\", stream->host);\n                }\n            }\n\n            url.setQuery(query);\n            return url.toString(QUrl::FullyEncoded);\n        }\n    }\n\n    QString NaiveBean::ToShareLink() {\n        QUrl url;\n        url.setScheme(\"naive+\" + protocol);\n        url.setUserName(username);\n        url.setPassword(password);\n        url.setHost(serverAddress);\n        url.setPort(serverPort);\n        if (!name.isEmpty()) url.setFragment(name);\n        return url.toString(QUrl::FullyEncoded);\n    }\n\n    QString QUICBean::ToShareLink() {\n        QUrl url;\n        if (proxy_type == proxy_TUIC) {\n            url.setScheme(\"tuic\");\n            url.setUserName(uuid);\n            url.setPassword(password);\n            url.setHost(serverAddress);\n            url.setPort(serverPort);\n\n            QUrlQuery q;\n            if (!congestionControl.isEmpty()) q.addQueryItem(\"congestion_control\", congestionControl);\n            if (!alpn.isEmpty()) q.addQueryItem(\"alpn\", alpn);\n            if (!sni.isEmpty()) q.addQueryItem(\"sni\", sni);\n            if (!udpRelayMode.isEmpty()) q.addQueryItem(\"udp_relay_mode\", udpRelayMode);\n            if (allowInsecure) q.addQueryItem(\"allow_insecure\", \"1\");\n            if (disableSni) q.addQueryItem(\"disable_sni\", \"1\");\n            if (!q.isEmpty()) url.setQuery(q);\n            if (!name.isEmpty()) url.setFragment(name);\n        } else if (proxy_type == proxy_Hysteria2) {\n            url.setScheme(\"hy2\");\n            url.setHost(serverAddress);\n            url.setPort(serverPort);\n            if (password.contains(\":\")) {\n                url.setUserName(SubStrBefore(password, \":\"));\n                url.setPassword(SubStrAfter(password, \":\"));\n            } else {\n                url.setUserName(password);\n            }\n            QUrlQuery q;\n            if (!obfsPassword.isEmpty()) {\n                q.addQueryItem(\"obfs\", \"salamander\");\n                q.addQueryItem(\"obfs-password\", obfsPassword);\n            }\n            if (!hopPort.trimmed().isEmpty()) q.addQueryItem(\"mport\", hopPort);\n            if (allowInsecure) q.addQueryItem(\"insecure\", \"1\");\n            if (!sni.isEmpty()) q.addQueryItem(\"sni\", sni);\n            if (!q.isEmpty()) url.setQuery(q);\n            if (!name.isEmpty()) url.setFragment(name);\n        }\n        return url.toString(QUrl::FullyEncoded);\n    }\n\n} // namespace NekoGui_fmt\n"
  },
  {
    "path": "fmt/ChainBean.hpp",
    "content": "#pragma once\n\n#include \"main/NekoGui.hpp\"\n\nnamespace NekoGui_fmt {\n    class ChainBean : public AbstractBean {\n    public:\n        QList<int> list; // in to out\n\n        ChainBean() : AbstractBean(0) {\n            _add(new configItem(\"list\", &list, itemType::integerList));\n        };\n\n        QString DisplayType() override { return QObject::tr(\"Chain Proxy\"); };\n\n        QString DisplayAddress() override { return \"\"; };\n    };\n} // namespace NekoGui_fmt\n"
  },
  {
    "path": "fmt/CustomBean.hpp",
    "content": "#pragma once\n\n#include \"fmt/AbstractBean.hpp\"\n\nnamespace NekoGui_fmt {\n    class CustomBean : public AbstractBean {\n    public:\n        QString core;\n        QList<QString> command;\n        QString config_suffix;\n        QString config_simple;\n        int mapping_port = 0;\n        int socks_port = 0;\n\n        CustomBean() : AbstractBean(0) {\n            _add(new configItem(\"core\", &core, itemType::string));\n            _add(new configItem(\"cmd\", &command, itemType::stringList));\n            _add(new configItem(\"cs\", &config_simple, itemType::string));\n            _add(new configItem(\"cs_suffix\", &config_suffix, itemType::string));\n            _add(new configItem(\"mapping_port\", &mapping_port, itemType::integer));\n            _add(new configItem(\"socks_port\", &socks_port, itemType::integer));\n        };\n\n        QString DisplayType() override {\n            if (core == \"internal\") {\n                auto obj = QString2QJsonObject(config_simple);\n                return obj[\"type\"].toString();\n            } else if (core == \"internal-full\") {\n                return software_core_name + \" config\";\n            }\n            return core;\n        };\n\n        QString DisplayCoreType() override { return NeedExternal(true) == 0 ? software_core_name : core; };\n\n        QString DisplayAddress() override {\n            if (core == \"internal\") {\n                auto obj = QString2QJsonObject(config_simple);\n                return ::DisplayAddress(obj[\"server\"].toString(), obj[\"server_port\"].toInt());\n            } else if (core == \"internal-full\") {\n                return {};\n            }\n            return AbstractBean::DisplayAddress();\n        };\n\n        int NeedExternal(bool isFirstProfile) override;\n\n        ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) override;\n\n        CoreObjOutboundBuildResult BuildCoreObjSingBox() override;\n    };\n} // namespace NekoGui_fmt"
  },
  {
    "path": "fmt/Link2Bean.cpp",
    "content": "#include \"db/ProxyEntity.hpp\"\n#include \"fmt/includes.h\"\n\n#include <QUrlQuery>\n\nnamespace NekoGui_fmt {\n\n#define DECODE_V2RAY_N_1                                                                                                        \\\n    QString linkN = DecodeB64IfValid(SubStrBefore(SubStrAfter(link, \"://\"), \"#\"), QByteArray::Base64Option::Base64UrlEncoding); \\\n    if (linkN.isEmpty()) return false;                                                                                          \\\n    auto hasRemarks = link.contains(\"#\");                                                                                       \\\n    if (hasRemarks) linkN += \"#\" + SubStrAfter(link, \"#\");                                                                      \\\n    auto url = QUrl(\"https://\" + linkN);\n\n    bool SocksHttpBean::TryParseLink(const QString &link) {\n        auto url = QUrl(link);\n        if (!url.isValid()) return false;\n        auto query = GetQuery(url);\n\n        if (link.startsWith(\"socks4\")) socks_http_type = type_Socks4;\n        if (link.startsWith(\"http\")) socks_http_type = type_HTTP;\n        name = url.fragment(QUrl::FullyDecoded);\n        serverAddress = url.host();\n        serverPort = url.port();\n        username = url.userName();\n        password = url.password();\n        if (serverPort == -1) serverPort = socks_http_type == type_HTTP ? 443 : 1080;\n\n        // v2rayN fmt\n        if (password.isEmpty() && !username.isEmpty()) {\n            QString n = DecodeB64IfValid(username);\n            if (!n.isEmpty()) {\n                username = SubStrBefore(n, \":\");\n                password = SubStrAfter(n, \":\");\n            }\n        }\n\n        stream->security = GetQueryValue(query, \"security\", \"\");\n        stream->sni = GetQueryValue(query, \"sni\");\n        if (link.startsWith(\"https\")) stream->security = \"tls\";\n\n        return !serverAddress.isEmpty();\n    }\n\n    bool TrojanVLESSBean::TryParseLink(const QString &link) {\n        auto url = QUrl(link);\n        if (!url.isValid()) return false;\n        auto query = GetQuery(url);\n\n        name = url.fragment(QUrl::FullyDecoded);\n        serverAddress = url.host();\n        serverPort = url.port();\n        password = url.userName();\n        if (serverPort == -1) serverPort = 443;\n\n        // security\n\n        auto type = GetQueryValue(query, \"type\", \"tcp\");\n        if (type == \"h2\") {\n            type = \"http\";\n        }\n        stream->network = type;\n\n        if (proxy_type == proxy_Trojan) {\n            stream->security = GetQueryValue(query, \"security\", \"tls\").replace(\"reality\", \"tls\").replace(\"none\", \"\");\n        } else {\n            stream->security = GetQueryValue(query, \"security\", \"\").replace(\"reality\", \"tls\").replace(\"none\", \"\");\n        }\n        auto sni1 = GetQueryValue(query, \"sni\");\n        auto sni2 = GetQueryValue(query, \"peer\");\n        if (!sni1.isEmpty()) stream->sni = sni1;\n        if (!sni2.isEmpty()) stream->sni = sni2;\n        stream->alpn = GetQueryValue(query, \"alpn\");\n        if (!query.queryItemValue(\"allowInsecure\").isEmpty()) stream->allow_insecure = true;\n        stream->reality_pbk = GetQueryValue(query, \"pbk\", \"\");\n        stream->reality_sid = GetQueryValue(query, \"sid\", \"\");\n        stream->reality_spx = GetQueryValue(query, \"spx\", \"\");\n        stream->utlsFingerprint = GetQueryValue(query, \"fp\", \"\");\n        if (stream->utlsFingerprint.isEmpty()) {\n            stream->utlsFingerprint = NekoGui::dataStore->utlsFingerprint;\n        }\n\n        // type\n        if (stream->network == \"ws\") {\n            stream->path = GetQueryValue(query, \"path\", \"\");\n            stream->host = GetQueryValue(query, \"host\", \"\");\n        } else if (stream->network == \"http\") {\n            stream->path = GetQueryValue(query, \"path\", \"\");\n            stream->host = GetQueryValue(query, \"host\", \"\").replace(\"|\", \",\");\n        } else if (stream->network == \"httpupgrade\") {\n            stream->path = GetQueryValue(query, \"path\", \"\");\n            stream->host = GetQueryValue(query, \"host\", \"\");\n        } else if (stream->network == \"grpc\") {\n            stream->path = GetQueryValue(query, \"serviceName\", \"\");\n        } else if (stream->network == \"tcp\") {\n            if (GetQueryValue(query, \"headerType\") == \"http\") {\n                stream->header_type = \"http\";\n                stream->host = GetQueryValue(query, \"host\", \"\");\n                stream->path = GetQueryValue(query, \"path\", \"\");\n            }\n        }\n\n        // protocol\n        if (proxy_type == proxy_VLESS) {\n            flow = GetQueryValue(query, \"flow\", \"\");\n        }\n\n        return !(password.isEmpty() || serverAddress.isEmpty());\n    }\n\n    bool ShadowSocksBean::TryParseLink(const QString &link) {\n        if (SubStrBefore(link, \"#\").contains(\"@\")) {\n            // SS\n            auto url = QUrl(link);\n            if (!url.isValid()) return false;\n\n            name = url.fragment(QUrl::FullyDecoded);\n            serverAddress = url.host();\n            serverPort = url.port();\n\n            if (url.password().isEmpty()) {\n                // traditional format\n                auto method_password = DecodeB64IfValid(url.userName(), QByteArray::Base64Option::Base64UrlEncoding);\n                if (method_password.isEmpty()) return false;\n                method = SubStrBefore(method_password, \":\");\n                password = SubStrAfter(method_password, \":\");\n            } else {\n                // 2022 format\n                method = url.userName();\n                password = url.password();\n            }\n\n            auto query = GetQuery(url);\n            plugin = query.queryItemValue(\"plugin\").replace(\"simple-obfs;\", \"obfs-local;\");\n        } else {\n            // v2rayN\n            DECODE_V2RAY_N_1\n\n            if (hasRemarks) name = url.fragment(QUrl::FullyDecoded);\n            serverAddress = url.host();\n            serverPort = url.port();\n            method = url.userName();\n            password = url.password();\n        }\n        return !(serverAddress.isEmpty() || method.isEmpty() || password.isEmpty());\n    }\n\n    bool VMessBean::TryParseLink(const QString &link) {\n        // V2RayN Format\n        auto linkN = DecodeB64IfValid(SubStrAfter(link, \"vmess://\"));\n        if (!linkN.isEmpty()) {\n            auto objN = QString2QJsonObject(linkN);\n            if (objN.isEmpty()) return false;\n            // REQUIRED\n            uuid = objN[\"id\"].toString();\n            serverAddress = objN[\"add\"].toString();\n            serverPort = objN[\"port\"].toVariant().toInt();\n            // OPTIONAL\n            name = objN[\"ps\"].toString();\n            aid = objN[\"aid\"].toVariant().toInt();\n            stream->host = objN[\"host\"].toString();\n            stream->path = objN[\"path\"].toString();\n            stream->sni = objN[\"sni\"].toString();\n            stream->header_type = objN[\"type\"].toString();\n            auto net = objN[\"net\"].toString();\n            if (!net.isEmpty()) {\n                if (net == \"h2\") {\n                    net = \"http\";\n                }\n                stream->network = net;\n            }\n            auto scy = objN[\"scy\"].toString();\n            if (!scy.isEmpty()) security = scy;\n            // TLS (XTLS?)\n            stream->security = objN[\"tls\"].toString();\n            // TODO quic & kcp\n            return true;\n        } else {\n            // https://github.com/XTLS/Xray-core/discussions/716\n            auto url = QUrl(link);\n            if (!url.isValid()) return false;\n            auto query = GetQuery(url);\n\n            name = url.fragment(QUrl::FullyDecoded);\n            serverAddress = url.host();\n            serverPort = url.port();\n            uuid = url.userName();\n            if (serverPort == -1) serverPort = 443;\n\n            aid = 0; // “此分享标准仅针对 VMess AEAD 和 VLESS。”\n            security = GetQueryValue(query, \"encryption\", \"auto\");\n\n            // security\n            auto type = GetQueryValue(query, \"type\", \"tcp\");\n            if (type == \"h2\") {\n                type = \"http\";\n            }\n            stream->network = type;\n            stream->security = GetQueryValue(query, \"security\", \"tls\").replace(\"reality\", \"tls\");\n            auto sni1 = GetQueryValue(query, \"sni\");\n            auto sni2 = GetQueryValue(query, \"peer\");\n            if (!sni1.isEmpty()) stream->sni = sni1;\n            if (!sni2.isEmpty()) stream->sni = sni2;\n            if (!query.queryItemValue(\"allowInsecure\").isEmpty()) stream->allow_insecure = true;\n            stream->reality_pbk = GetQueryValue(query, \"pbk\", \"\");\n            stream->reality_sid = GetQueryValue(query, \"sid\", \"\");\n            stream->reality_spx = GetQueryValue(query, \"spx\", \"\");\n            stream->utlsFingerprint = GetQueryValue(query, \"fp\", \"\");\n            if (stream->utlsFingerprint.isEmpty()) {\n                stream->utlsFingerprint = NekoGui::dataStore->utlsFingerprint;\n            }\n\n            // type\n            if (stream->network == \"ws\") {\n                stream->path = GetQueryValue(query, \"path\", \"\");\n                stream->host = GetQueryValue(query, \"host\", \"\");\n            } else if (stream->network == \"http\") {\n                stream->path = GetQueryValue(query, \"path\", \"\");\n                stream->host = GetQueryValue(query, \"host\", \"\").replace(\"|\", \",\");\n            } else if (stream->network == \"httpupgrade\") {\n                stream->path = GetQueryValue(query, \"path\", \"\");\n                stream->host = GetQueryValue(query, \"host\", \"\");\n            } else if (stream->network == \"grpc\") {\n                stream->path = GetQueryValue(query, \"serviceName\", \"\");\n            } else if (stream->network == \"tcp\") {\n                if (GetQueryValue(query, \"headerType\") == \"http\") {\n                    stream->header_type = \"http\";\n                    stream->path = GetQueryValue(query, \"path\", \"\");\n                    stream->host = GetQueryValue(query, \"host\", \"\");\n                }\n            }\n            return !(uuid.isEmpty() || serverAddress.isEmpty());\n        }\n\n        return false;\n    }\n\n    bool NaiveBean::TryParseLink(const QString &link) {\n        auto url = QUrl(link);\n        if (!url.isValid()) return false;\n\n        protocol = url.scheme().replace(\"naive+\", \"\");\n        if (protocol != \"https\" && protocol != \"quic\") return false;\n\n        name = url.fragment(QUrl::FullyDecoded);\n        serverAddress = url.host();\n        serverPort = url.port();\n        username = url.userName();\n        password = url.password();\n\n        return !(username.isEmpty() || password.isEmpty() || serverAddress.isEmpty());\n    }\n\n    bool QUICBean::TryParseLink(const QString &link) {\n        auto url = QUrl(link);\n        auto query = QUrlQuery(url.query());\n        if (url.host().isEmpty() || url.port() == -1) return false;\n\n        if (url.scheme() == \"tuic\") {\n            // by daeuniverse\n            // https://github.com/daeuniverse/dae/discussions/182\n\n            name = url.fragment(QUrl::FullyDecoded);\n            serverAddress = url.host();\n            if (serverPort == -1) serverPort = 443;\n            serverPort = url.port();\n\n            uuid = url.userName();\n            password = url.password();\n\n            congestionControl = query.queryItemValue(\"congestion_control\");\n            alpn = query.queryItemValue(\"alpn\");\n            sni = query.queryItemValue(\"sni\");\n            udpRelayMode = query.queryItemValue(\"udp_relay_mode\");\n            allowInsecure = query.queryItemValue(\"allow_insecure\") == \"1\";\n            disableSni = query.queryItemValue(\"disable_sni\") == \"1\";\n        } else if (QStringList{\"hy2\", \"hysteria2\"}.contains(url.scheme())) {\n            name = url.fragment(QUrl::FullyDecoded);\n            serverAddress = url.host();\n            serverPort = url.port();\n            hopPort = query.queryItemValue(\"mport\");\n            obfsPassword = query.queryItemValue(\"obfs-password\");\n            allowInsecure = QStringList{\"1\", \"true\"}.contains(query.queryItemValue(\"insecure\"));\n\n            if (url.password().isEmpty()) {\n                password = url.userName();\n            } else {\n                password = url.userName() + \":\" + url.password();\n            }\n\n            sni = query.queryItemValue(\"sni\");\n        }\n\n        return true;\n    }\n\n} // namespace NekoGui_fmt"
  },
  {
    "path": "fmt/NaiveBean.hpp",
    "content": "#pragma once\n\n#include \"fmt/AbstractBean.hpp\"\n\nnamespace NekoGui_fmt {\n    class NaiveBean : public AbstractBean {\n    public:\n        QString username = \"\";\n        QString password = \"\";\n        QString protocol = \"https\";\n        QString extra_headers = \"\";\n        QString sni = \"\";\n        QString certificate = \"\";\n        int insecure_concurrency = 0;\n\n        bool disable_log = false;\n\n        NaiveBean() : AbstractBean(0) {\n            _add(new configItem(\"username\", &username, itemType::string));\n            _add(new configItem(\"password\", &password, itemType::string));\n            _add(new configItem(\"protocol\", &protocol, itemType::string));\n            _add(new configItem(\"extra_headers\", &extra_headers, itemType::string));\n            _add(new configItem(\"sni\", &sni, itemType::string));\n            _add(new configItem(\"certificate\", &certificate, itemType::string));\n            _add(new configItem(\"insecure_concurrency\", &insecure_concurrency, itemType::integer));\n            _add(new configItem(\"disable_log\", &disable_log, itemType::boolean));\n        };\n\n        QString DisplayCoreType() override { return \"Naive\"; };\n\n        QString DisplayType() override { return \"Naive\"; };\n\n        int NeedExternal(bool isFirstProfile) override;\n\n        ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) override;\n\n        bool TryParseLink(const QString &link);\n\n        QString ToShareLink() override;\n    };\n} // namespace NekoGui_fmt"
  },
  {
    "path": "fmt/Preset.hpp",
    "content": "#pragma once\n\nnamespace Preset {\n    namespace SingBox {\n        inline QStringList VpnImplementation = {\"gvisor\", \"system\", \"mixed\"};\n        inline QStringList DomainStrategy = {\"\", \"ipv4_only\", \"ipv6_only\", \"prefer_ipv4\", \"prefer_ipv6\"};\n        inline QStringList UtlsFingerPrint = {\"\", \"chrome\", \"firefox\", \"edge\", \"safari\", \"360\", \"qq\", \"ios\", \"android\", \"random\", \"randomized\"};\n        inline QStringList ShadowsocksMethods = {\"2022-blake3-aes-128-gcm\", \"2022-blake3-aes-256-gcm\", \"2022-blake3-chacha20-poly1305\", \"none\", \"aes-128-gcm\", \"aes-192-gcm\", \"aes-256-gcm\", \"chacha20-ietf-poly1305\", \"xchacha20-ietf-poly1305\", \"aes-128-ctr\", \"aes-192-ctr\", \"aes-256-ctr\", \"aes-128-cfb\", \"aes-192-cfb\", \"aes-256-cfb\", \"rc4-md5\", \"chacha20-ietf\", \"xchacha20\"};\n        inline QStringList Flows = {\"xtls-rprx-vision\"};\n    } // namespace SingBox\n\n    namespace Windows {\n        inline QStringList system_proxy_format{\"{ip}:{http_port}\",\n                                               \"socks={ip}:{socks_port}\",\n                                               \"http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}\",\n                                               \"http=http://{ip}:{http_port};https=http://{ip}:{http_port}\"};\n    } // namespace Windows\n} // namespace Preset\n"
  },
  {
    "path": "fmt/QUICBean.hpp",
    "content": "#pragma once\n\n#include \"fmt/AbstractBean.hpp\"\n\nnamespace NekoGui_fmt {\n    class QUICBean : public AbstractBean {\n    public:\n        // static constexpr int proxy_Hysteria = 0;\n        static constexpr int proxy_TUIC = 1;\n        static constexpr int proxy_Hysteria2 = 3;\n        int proxy_type = proxy_Hysteria2;\n\n        bool forceExternal = false;\n\n        // Hysteria 2\n\n        QString obfsPassword = \"\";\n\n        int uploadMbps = 0;\n        int downloadMbps = 0;\n\n        qint64 streamReceiveWindow = 0;\n        qint64 connectionReceiveWindow = 0;\n        bool disableMtuDiscovery = false;\n\n        int hopInterval = 10;\n        QString hopPort = \"\";\n\n        // TUIC\n\n        QString uuid = \"\";\n        QString congestionControl = \"bbr\";\n        QString udpRelayMode = \"native\";\n        bool zeroRttHandshake = false;\n        QString heartbeat = \"10s\";\n        bool uos = false;\n\n        // HY2&TUIC\n\n        QString password = \"\";\n\n        // TLS\n\n        bool allowInsecure = false;\n        QString sni = \"\";\n        QString alpn = \"\";\n        QString caText = \"\";\n        bool disableSni = false;\n\n        explicit QUICBean(int _proxy_type) : AbstractBean(0) {\n            proxy_type = _proxy_type;\n            if (proxy_type == proxy_Hysteria2) {\n                _add(new configItem(\"obfsPassword\", &obfsPassword, itemType::string));\n                _add(new configItem(\"uploadMbps\", &uploadMbps, itemType::integer));\n                _add(new configItem(\"downloadMbps\", &downloadMbps, itemType::integer));\n                _add(new configItem(\"streamReceiveWindow\", &streamReceiveWindow, itemType::integer64));\n                _add(new configItem(\"connectionReceiveWindow\", &connectionReceiveWindow, itemType::integer64));\n                _add(new configItem(\"disableMtuDiscovery\", &disableMtuDiscovery, itemType::boolean));\n                _add(new configItem(\"hopInterval\", &hopInterval, itemType::integer));\n                _add(new configItem(\"hopPort\", &hopPort, itemType::string));\n                _add(new configItem(\"password\", &password, itemType::string));\n            } else if (proxy_type == proxy_TUIC) {\n                _add(new configItem(\"uuid\", &uuid, itemType::string));\n                _add(new configItem(\"password\", &password, itemType::string));\n                _add(new configItem(\"congestionControl\", &congestionControl, itemType::string));\n                _add(new configItem(\"udpRelayMode\", &udpRelayMode, itemType::string));\n                _add(new configItem(\"zeroRttHandshake\", &zeroRttHandshake, itemType::boolean));\n                _add(new configItem(\"heartbeat\", &heartbeat, itemType::string));\n                _add(new configItem(\"uos\", &uos, itemType::boolean));\n            }\n            _add(new configItem(\"forceExternal\", &forceExternal, itemType::boolean));\n            // TLS\n            _add(new configItem(\"allowInsecure\", &allowInsecure, itemType::boolean));\n            _add(new configItem(\"sni\", &sni, itemType::string));\n            _add(new configItem(\"alpn\", &alpn, itemType::string));\n            _add(new configItem(\"caText\", &caText, itemType::string));\n            _add(new configItem(\"disableSni\", &disableSni, itemType::boolean));\n        };\n\n        QString DisplayAddress() override {\n            if (!hopPort.trimmed().isEmpty()) return WrapIPV6Host(serverAddress) + \":\" + hopPort;\n            return ::DisplayAddress(serverAddress, serverPort);\n        }\n\n        QString DisplayCoreType() override {\n            if (NeedExternal(true) == 0) {\n                return software_core_name;\n            } else if (proxy_type == proxy_TUIC) {\n                return \"tuic\";\n            } else {\n                return \"hysteria2\";\n            }\n        }\n\n        QString DisplayType() override {\n            if (proxy_type == proxy_TUIC) {\n                return \"TUIC\";\n            } else {\n                return \"Hysteria2\";\n            }\n        };\n\n        int NeedExternal(bool isFirstProfile) override;\n\n        ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) override;\n\n        CoreObjOutboundBuildResult BuildCoreObjSingBox() override;\n\n        bool TryParseLink(const QString &link);\n\n        QString ToShareLink() override;\n    };\n} // namespace NekoGui_fmt"
  },
  {
    "path": "fmt/ShadowSocksBean.hpp",
    "content": "#pragma once\n\n#include \"fmt/AbstractBean.hpp\"\n#include \"fmt/V2RayStreamSettings.hpp\"\n\nnamespace NekoGui_fmt {\n    class ShadowSocksBean : public AbstractBean {\n    public:\n        QString method = \"aes-128-gcm\";\n        QString password = \"\";\n        QString plugin = \"\";\n        int uot = 0;\n\n        std::shared_ptr<V2rayStreamSettings> stream = std::make_shared<V2rayStreamSettings>();\n\n        ShadowSocksBean() : AbstractBean(0) {\n            _add(new configItem(\"method\", &method, itemType::string));\n            _add(new configItem(\"pass\", &password, itemType::string));\n            _add(new configItem(\"plugin\", &plugin, itemType::string));\n            _add(new configItem(\"uot\", &uot, itemType::integer));\n            _add(new configItem(\"stream\", dynamic_cast<JsonStore *>(stream.get()), itemType::jsonStore));\n        };\n\n        QString DisplayType() override { return \"Shadowsocks\"; };\n\n        CoreObjOutboundBuildResult BuildCoreObjSingBox() override;\n\n        bool TryParseLink(const QString &link);\n\n        QString ToShareLink() override;\n    };\n} // namespace NekoGui_fmt\n"
  },
  {
    "path": "fmt/SocksHttpBean.hpp",
    "content": "#pragma once\n\n#include \"fmt/AbstractBean.hpp\"\n#include \"fmt/V2RayStreamSettings.hpp\"\n\nnamespace NekoGui_fmt {\n    class SocksHttpBean : public AbstractBean {\n    public:\n        static constexpr int type_HTTP = -80;\n        static constexpr int type_Socks4 = 4;\n        static constexpr int type_Socks5 = 5;\n\n        int socks_http_type = type_Socks5;\n        QString username = \"\";\n        QString password = \"\";\n\n        std::shared_ptr<V2rayStreamSettings> stream = std::make_shared<V2rayStreamSettings>();\n\n        explicit SocksHttpBean(int _socks_http_type) : AbstractBean(0) {\n            this->socks_http_type = _socks_http_type;\n            _add(new configItem(\"v\", &socks_http_type, itemType::integer));\n            _add(new configItem(\"username\", &username, itemType::string));\n            _add(new configItem(\"password\", &password, itemType::string));\n            _add(new configItem(\"stream\", dynamic_cast<JsonStore *>(stream.get()), itemType::jsonStore));\n        };\n\n        QString DisplayType() override { return socks_http_type == type_HTTP ? \"HTTP\" : \"Socks\"; };\n\n        CoreObjOutboundBuildResult BuildCoreObjSingBox() override;\n\n        bool TryParseLink(const QString &link);\n\n        QString ToShareLink() override;\n    };\n} // namespace NekoGui_fmt\n"
  },
  {
    "path": "fmt/TrojanVLESSBean.hpp",
    "content": "#pragma once\n\n#include \"fmt/AbstractBean.hpp\"\n#include \"fmt/V2RayStreamSettings.hpp\"\n\nnamespace NekoGui_fmt {\n    class TrojanVLESSBean : public AbstractBean {\n    public:\n        static constexpr int proxy_Trojan = 0;\n        static constexpr int proxy_VLESS = 1;\n        int proxy_type = proxy_Trojan;\n\n        QString password = \"\";\n        QString flow = \"\";\n\n        std::shared_ptr<V2rayStreamSettings> stream = std::make_shared<V2rayStreamSettings>();\n\n        explicit TrojanVLESSBean(int _proxy_type) : AbstractBean(0) {\n            proxy_type = _proxy_type;\n            _add(new configItem(\"pass\", &password, itemType::string));\n            _add(new configItem(\"flow\", &flow, itemType::string));\n            _add(new configItem(\"stream\", dynamic_cast<JsonStore *>(stream.get()), itemType::jsonStore));\n        };\n\n        QString DisplayType() override { return proxy_type == proxy_VLESS ? \"VLESS\" : \"Trojan\"; };\n\n        CoreObjOutboundBuildResult BuildCoreObjSingBox() override;\n\n        bool TryParseLink(const QString &link);\n\n        QString ToShareLink() override;\n    };\n} // namespace NekoGui_fmt"
  },
  {
    "path": "fmt/V2RayStreamSettings.hpp",
    "content": "#pragma once\n\n#include \"AbstractBean.hpp\"\n\nnamespace NekoGui_fmt {\n    class V2rayStreamSettings : public JsonStore {\n    public:\n        QString network = \"tcp\";\n        QString security = \"\";\n        QString packet_encoding = \"\";\n        // ws/http/grpc/tcp-http/httpupgrade\n        QString path = \"\";\n        QString host = \"\";\n        // kcp/quic/tcp-http\n        QString header_type = \"\";\n        // tls\n        QString sni = \"\";\n        QString alpn = \"\";\n        QString certificate = \"\";\n        QString utlsFingerprint = \"\";\n        bool allow_insecure = false;\n        // ws early data\n        QString ws_early_data_name = \"\";\n        int ws_early_data_length = 0;\n        // reality\n        QString reality_pbk = \"\";\n        QString reality_sid = \"\";\n        QString reality_spx = \"\";\n        // multiplex\n        int multiplex_status = 0;\n\n        V2rayStreamSettings() : JsonStore() {\n            _add(new configItem(\"net\", &network, itemType::string));\n            _add(new configItem(\"sec\", &security, itemType::string));\n            _add(new configItem(\"pac_enc\", &packet_encoding, itemType::string));\n            _add(new configItem(\"path\", &path, itemType::string));\n            _add(new configItem(\"host\", &host, itemType::string));\n            _add(new configItem(\"sni\", &sni, itemType::string));\n            _add(new configItem(\"alpn\", &alpn, itemType::string));\n            _add(new configItem(\"cert\", &certificate, itemType::string));\n            _add(new configItem(\"insecure\", &allow_insecure, itemType::boolean));\n            _add(new configItem(\"h_type\", &header_type, itemType::string));\n            _add(new configItem(\"ed_name\", &ws_early_data_name, itemType::string));\n            _add(new configItem(\"ed_len\", &ws_early_data_length, itemType::integer));\n            _add(new configItem(\"utls\", &utlsFingerprint, itemType::string));\n            _add(new configItem(\"pbk\", &reality_pbk, itemType::string));\n            _add(new configItem(\"sid\", &reality_sid, itemType::string));\n            _add(new configItem(\"spx\", &reality_spx, itemType::string));\n            _add(new configItem(\"mux_s\", &multiplex_status, itemType::integer));\n        }\n\n        void BuildStreamSettingsSingBox(QJsonObject *outbound);\n    };\n\n    inline V2rayStreamSettings *GetStreamSettings(AbstractBean *bean) {\n        if (bean == nullptr) return nullptr;\n        auto stream_item = bean->_get(\"stream\");\n        if (stream_item != nullptr) {\n            auto stream_store = (JsonStore *) stream_item->ptr;\n            auto stream = (NekoGui_fmt::V2rayStreamSettings *) stream_store;\n            return stream;\n        }\n        return nullptr;\n    }\n} // namespace NekoGui_fmt\n"
  },
  {
    "path": "fmt/VMessBean.hpp",
    "content": "#pragma once\n\n#include \"fmt/AbstractBean.hpp\"\n#include \"fmt/V2RayStreamSettings.hpp\"\n\nnamespace NekoGui_fmt {\n    class VMessBean : public AbstractBean {\n    public:\n        QString uuid = \"\";\n        int aid = 0;\n        QString security = \"auto\";\n\n        std::shared_ptr<V2rayStreamSettings> stream = std::make_shared<V2rayStreamSettings>();\n\n        VMessBean() : AbstractBean(0) {\n            _add(new configItem(\"id\", &uuid, itemType::string));\n            _add(new configItem(\"aid\", &aid, itemType::integer));\n            _add(new configItem(\"sec\", &security, itemType::string));\n            _add(new configItem(\"stream\", dynamic_cast<JsonStore *>(stream.get()), itemType::jsonStore));\n        };\n\n        QString DisplayType() override { return \"VMess\"; };\n\n        CoreObjOutboundBuildResult BuildCoreObjSingBox() override;\n\n        bool TryParseLink(const QString &link);\n\n        QString ToShareLink() override;\n    };\n} // namespace NekoGui_fmt\n"
  },
  {
    "path": "fmt/includes.h",
    "content": "#pragma once\n\n#include \"SocksHttpBean.hpp\"\n#include \"ShadowSocksBean.hpp\"\n#include \"ChainBean.hpp\"\n#include \"VMessBean.hpp\"\n#include \"TrojanVLESSBean.hpp\"\n#include \"NaiveBean.hpp\"\n#include \"QUICBean.hpp\"\n#include \"CustomBean.hpp\"\n"
  },
  {
    "path": "go/.gitignore",
    "content": "*.log\n*.pem\n*.json\n*.exe\n*.dat\n/cmd/nekoray_core/nekoray_core\n/cmd/nekobox_core/nekobox_core\n*.db\n"
  },
  {
    "path": "go/cmd/nekobox_core/core_box.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\n\t\"github.com/matsuridayo/libneko/neko_common\"\n\t\"github.com/matsuridayo/libneko/neko_log\"\n\tbox \"github.com/sagernet/sing-box\"\n\t\"github.com/sagernet/sing-box/boxapi\"\n\tboxmain \"github.com/sagernet/sing-box/cmd/sing-box\"\n)\n\nvar instance *box.Box\nvar instance_cancel context.CancelFunc\n\nfunc setupCore() {\n\tboxmain.SetDisableColor(true)\n\t//\n\tneko_log.SetupLog(50*1024, \"./neko.log\")\n\t//\n\tneko_common.GetCurrentInstance = func() interface{} {\n\t\treturn instance\n\t}\n\tneko_common.DialContext = func(ctx context.Context, specifiedInstance interface{}, network, addr string) (net.Conn, error) {\n\t\tif i, ok := specifiedInstance.(*box.Box); ok {\n\t\t\treturn boxapi.DialContext(ctx, i, network, addr)\n\t\t}\n\t\tif instance != nil {\n\t\t\treturn boxapi.DialContext(ctx, instance, network, addr)\n\t\t}\n\t\treturn neko_common.DialContextSystem(ctx, network, addr)\n\t}\n\tneko_common.DialUDP = func(ctx context.Context, specifiedInstance interface{}) (net.PacketConn, error) {\n\t\tif i, ok := specifiedInstance.(*box.Box); ok {\n\t\t\treturn boxapi.DialUDP(ctx, i)\n\t\t}\n\t\tif instance != nil {\n\t\t\treturn boxapi.DialUDP(ctx, instance)\n\t\t}\n\t\treturn neko_common.DialUDPSystem(ctx)\n\t}\n\tneko_common.CreateProxyHttpClient = func(specifiedInstance interface{}) *http.Client {\n\t\tif i, ok := specifiedInstance.(*box.Box); ok {\n\t\t\treturn boxapi.CreateProxyHttpClient(i)\n\t\t}\n\t\treturn boxapi.CreateProxyHttpClient(instance)\n\t}\n}\n"
  },
  {
    "path": "go/cmd/nekobox_core/go.mod",
    "content": "module nekobox_core\n\ngo 1.19\n\nrequire (\n\tgithub.com/matsuridayo/libneko v1.0.0 // replaced\n\tgithub.com/sagernet/sing-box v1.0.0 // replaced\n\t// github.com/sagernet/sing-dns v1.0.0 // indirect; replaced\n\tgrpc_server v1.0.0\n)\n\nrequire (\n\tberty.tech/go-libtor v1.0.385 // indirect\n\tcloud.google.com/go/compute/metadata v0.2.3 // indirect\n\tgithub.com/ajg/form v1.5.1 // indirect\n\tgithub.com/andybalholm/brotli v1.0.6 // indirect\n\tgithub.com/caddyserver/certmagic v0.20.0 // indirect\n\tgithub.com/cloudflare/circl v1.3.7 // indirect\n\tgithub.com/cretz/bine v0.2.0 // indirect\n\tgithub.com/fsnotify/fsnotify v1.7.0 // indirect\n\tgithub.com/gaukas/godicttls v0.0.4 // indirect\n\tgithub.com/go-chi/chi/v5 v5.0.12 // indirect\n\tgithub.com/go-chi/cors v1.2.1 // 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/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // 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.2.0 // indirect\n\tgithub.com/google/btree v1.1.2 // indirect\n\tgithub.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect\n\tgithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect\n\tgithub.com/hashicorp/yamux v0.1.1 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect\n\tgithub.com/josharian/native v1.1.0 // indirect\n\tgithub.com/klauspost/compress v1.17.4 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.5 // indirect\n\tgithub.com/libdns/alidns v1.0.3 // indirect\n\tgithub.com/libdns/cloudflare v0.1.1 // indirect\n\tgithub.com/libdns/libdns v0.2.2 // indirect\n\tgithub.com/logrusorgru/aurora v2.0.3+incompatible // indirect\n\tgithub.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d // indirect\n\tgithub.com/mholt/acmez v1.2.0 // indirect\n\tgithub.com/miekg/dns v1.1.59 // indirect\n\tgithub.com/onsi/ginkgo/v2 v2.9.7 // indirect\n\tgithub.com/ooni/go-libtor v1.1.8 // indirect\n\tgithub.com/oschwald/maxminddb-golang v1.12.0 // indirect\n\tgithub.com/pierrec/lz4/v4 v4.1.14 // indirect\n\tgithub.com/quic-go/qpack v0.4.0 // indirect\n\tgithub.com/quic-go/qtls-go1-20 v0.4.1 // indirect\n\tgithub.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect\n\tgithub.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect\n\tgithub.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f // indirect\n\tgithub.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // indirect\n\tgithub.com/sagernet/quic-go v0.47.0-beta.2 // indirect\n\tgithub.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect\n\tgithub.com/sagernet/sing v0.4.3 // indirect\n\tgithub.com/sagernet/sing-dns v0.2.3 // indirect\n\tgithub.com/sagernet/sing-mux v0.2.0 // indirect\n\tgithub.com/sagernet/sing-quic v0.2.2 // indirect\n\tgithub.com/sagernet/sing-shadowsocks v0.2.7 // indirect\n\tgithub.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect\n\tgithub.com/sagernet/sing-shadowtls v0.1.4 // indirect\n\tgithub.com/sagernet/sing-tun v0.3.3 // indirect\n\tgithub.com/sagernet/sing-vmess v0.1.12 // indirect\n\tgithub.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect\n\tgithub.com/sagernet/utls v1.5.4 // indirect\n\tgithub.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect\n\tgithub.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect\n\tgithub.com/spf13/cobra v1.8.0 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect\n\tgithub.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect\n\tgithub.com/zeebo/blake3 v0.2.3 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgo4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect\n\tgolang.org/x/crypto v0.23.0 // indirect\n\tgolang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect\n\tgolang.org/x/mod v0.18.0 // indirect\n\tgolang.org/x/net v0.25.0 // indirect\n\tgolang.org/x/sync v0.8.0 // indirect\n\tgolang.org/x/sys v0.25.0 // indirect\n\tgolang.org/x/text v0.18.0 // indirect\n\tgolang.org/x/time v0.5.0 // indirect\n\tgolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect\n\tgolang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect\n\tgoogle.golang.org/grpc v1.63.2 // indirect\n\tgoogle.golang.org/protobuf v1.33.0 // indirect\n\tlukechampine.com/blake3 v1.3.0 // indirect\n)\n\nreplace grpc_server => ../../grpc_server\n\nreplace github.com/matsuridayo/libneko => ../../../../libneko\n\nreplace github.com/sagernet/sing-box => ../../../../sing-box\n\nreplace github.com/sagernet/sing-quic => ../../../../sing-quic\n\n// replace github.com/sagernet/sing => ../../../../sing\n\n// replace github.com/sagernet/sing-dns => ../../../../sing-dns\n"
  },
  {
    "path": "go/cmd/nekobox_core/go.sum",
    "content": "berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=\nberty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=\ncloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=\ncloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=\ncloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.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.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=\ngithub.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=\ngithub.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=\ngithub.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=\ngithub.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=\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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\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/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=\ngithub.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=\ngithub.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=\ngithub.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=\ngithub.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=\ngithub.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=\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-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=\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/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=\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.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM=\ngithub.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=\ngithub.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=\ngithub.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=\ngithub.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=\ngithub.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=\ngithub.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=\ngithub.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=\ngithub.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=\ngithub.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=\ngithub.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=\ngithub.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=\ngithub.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=\ngithub.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=\ngithub.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=\ngithub.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=\ngithub.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=\ngithub.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=\ngithub.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=\ngithub.com/libdns/libdns v0.2.2/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/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d h1:j9LtzkYstLFoNvXW824QQeN7Y26uPL5249kzWKbzO9U=\ngithub.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=\ngithub.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=\ngithub.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=\ngithub.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=\ngithub.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=\ngithub.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=\ngithub.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=\ngithub.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=\ngithub.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w=\ngithub.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI=\ngithub.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=\ngithub.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=\ngithub.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=\ngithub.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=\ngithub.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=\ngithub.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=\ngithub.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=\ngithub.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\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/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=\ngithub.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k=\ngithub.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I=\ngithub.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0=\ngithub.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk=\ngithub.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=\ngithub.com/sagernet/quic-go v0.47.0-beta.2 h1:1tCGWFOSaXIeuQaHrwOMJIYvlupjTcaVInGQw5ArULU=\ngithub.com/sagernet/quic-go v0.47.0-beta.2/go.mod h1:bLVKvElSEMNv7pu7SZHscW02TYigzQ5lQu3Nh4wNh8Q=\ngithub.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=\ngithub.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=\ngithub.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=\ngithub.com/sagernet/sing v0.4.3 h1:Ty/NAiNnVd6844k7ujlL5lkzydhcTH5Psc432jXA4Y8=\ngithub.com/sagernet/sing v0.4.3/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=\ngithub.com/sagernet/sing-dns v0.2.3 h1:YzeBUn2tR38F7HtvGEQ0kLRLmZWMEgi/+7wqa4Twb1k=\ngithub.com/sagernet/sing-dns v0.2.3/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg=\ngithub.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=\ngithub.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=\ngithub.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=\ngithub.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=\ngithub.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=\ngithub.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=\ngithub.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=\ngithub.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=\ngithub.com/sagernet/sing-tun v0.3.3 h1:LZnQNmfGcNG2KPTPkLgc+Lo7k606QJVkPp2DnjriwUk=\ngithub.com/sagernet/sing-tun v0.3.3/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ=\ngithub.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg=\ngithub.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I=\ngithub.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=\ngithub.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=\ngithub.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co=\ngithub.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s=\ngithub.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs=\ngithub.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc=\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/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=\ngithub.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=\ngithub.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=\ngithub.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=\ngithub.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=\ngithub.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\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.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=\ngithub.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=\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.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\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.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=\ngolang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=\ngolang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=\ngolang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=\ngolang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=\ngolang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=\ngolang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\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.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=\ngolang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=\ngolang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=\ngolang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=\ngolang.org/x/time v0.5.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.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=\ngolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=\ngolang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=\ngoogle.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=\ngoogle.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=\ngoogle.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\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=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nlukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=\nlukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=\n"
  },
  {
    "path": "go/cmd/nekobox_core/grpc_box.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"grpc_server\"\n\t\"grpc_server/gen\"\n\n\t\"github.com/matsuridayo/libneko/neko_common\"\n\t\"github.com/matsuridayo/libneko/neko_log\"\n\t\"github.com/matsuridayo/libneko/speedtest\"\n\tbox \"github.com/sagernet/sing-box\"\n\t\"github.com/sagernet/sing-box/boxapi\"\n\tboxmain \"github.com/sagernet/sing-box/cmd/sing-box\"\n\n\t\"log\"\n\n\t\"github.com/sagernet/sing-box/option\"\n)\n\ntype server struct {\n\tgrpc_server.BaseServer\n}\n\nfunc (s *server) Start(ctx context.Context, in *gen.LoadConfigReq) (out *gen.ErrorResp, _ error) {\n\tvar err error\n\n\tdefer func() {\n\t\tout = &gen.ErrorResp{}\n\t\tif err != nil {\n\t\t\tout.Error = err.Error()\n\t\t\tinstance = nil\n\t\t}\n\t}()\n\n\tif neko_common.Debug {\n\t\tlog.Println(\"Start:\", in.CoreConfig)\n\t}\n\n\tif instance != nil {\n\t\terr = errors.New(\"instance already started\")\n\t\treturn\n\t}\n\n\tinstance, instance_cancel, err = boxmain.Create([]byte(in.CoreConfig))\n\n\tif instance != nil {\n\t\t// Logger\n\t\tinstance.SetLogWritter(neko_log.LogWriter)\n\t\t// V2ray Service\n\t\tif in.StatsOutbounds != nil {\n\t\t\tinstance.Router().SetV2RayServer(boxapi.NewSbV2rayServer(option.V2RayStatsServiceOptions{\n\t\t\t\tEnabled:   true,\n\t\t\t\tOutbounds: in.StatsOutbounds,\n\t\t\t}))\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (s *server) Stop(ctx context.Context, in *gen.EmptyReq) (out *gen.ErrorResp, _ error) {\n\tvar err error\n\n\tdefer func() {\n\t\tout = &gen.ErrorResp{}\n\t\tif err != nil {\n\t\t\tout.Error = err.Error()\n\t\t}\n\t}()\n\n\tif instance == nil {\n\t\treturn\n\t}\n\n\tinstance_cancel()\n\tinstance.Close()\n\n\tinstance = nil\n\n\treturn\n}\n\nfunc (s *server) Test(ctx context.Context, in *gen.TestReq) (out *gen.TestResp, _ error) {\n\tvar err error\n\tout = &gen.TestResp{Ms: 0}\n\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tout.Error = err.Error()\n\t\t}\n\t}()\n\n\tif in.Mode == gen.TestMode_UrlTest {\n\t\tvar i *box.Box\n\t\tvar cancel context.CancelFunc\n\t\tif in.Config != nil {\n\t\t\t// Test instance\n\t\t\ti, cancel, err = boxmain.Create([]byte(in.Config.CoreConfig))\n\t\t\tif i != nil {\n\t\t\t\tdefer i.Close()\n\t\t\t\tdefer cancel()\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\t// Test running instance\n\t\t\ti = instance\n\t\t\tif i == nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\t// Latency\n\t\tout.Ms, err = speedtest.UrlTest(boxapi.CreateProxyHttpClient(i), in.Url, in.Timeout, speedtest.UrlTestStandard_RTT)\n\t} else if in.Mode == gen.TestMode_TcpPing {\n\t\tout.Ms, err = speedtest.TcpPing(in.Address, in.Timeout)\n\t} else if in.Mode == gen.TestMode_FullTest {\n\t\ti, cancel, err := boxmain.Create([]byte(in.Config.CoreConfig))\n\t\tif i != nil {\n\t\t\tdefer i.Close()\n\t\t\tdefer cancel()\n\t\t}\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\treturn grpc_server.DoFullTest(ctx, in, i)\n\t}\n\n\treturn\n}\n\nfunc (s *server) QueryStats(ctx context.Context, in *gen.QueryStatsReq) (out *gen.QueryStatsResp, _ error) {\n\tout = &gen.QueryStatsResp{}\n\n\tif instance != nil {\n\t\tif ss, ok := instance.Router().V2RayServer().(*boxapi.SbV2rayServer); ok {\n\t\t\tout.Traffic = ss.QueryStats(fmt.Sprintf(\"outbound>>>%s>>>traffic>>>%s\", in.Tag, in.Direct))\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (s *server) ListConnections(ctx context.Context, in *gen.EmptyReq) (*gen.ListConnectionsResp, error) {\n\tout := &gen.ListConnectionsResp{\n\t\t// TODO upstream api\n\t}\n\treturn out, nil\n}\n"
  },
  {
    "path": "go/cmd/nekobox_core/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t_ \"unsafe\"\n\n\t\"grpc_server\"\n\n\t\"github.com/matsuridayo/libneko/neko_common\"\n\tboxmain \"github.com/sagernet/sing-box/cmd/sing-box\"\n\t\"github.com/sagernet/sing-box/constant\"\n)\n\nfunc main() {\n\tfmt.Println(\"sing-box:\", constant.Version, \"NekoBox:\", neko_common.Version_neko)\n\tfmt.Println()\n\n\t// nekobox_core\n\tif len(os.Args) > 1 && os.Args[1] == \"nekobox\" {\n\t\tneko_common.RunMode = neko_common.RunMode_NekoBox_Core\n\t\tgrpc_server.RunCore(setupCore, &server{})\n\t\treturn\n\t}\n\n\t// sing-box\n\tboxmain.Main()\n}\n"
  },
  {
    "path": "go/cmd/updater/.gitignore",
    "content": "/updater\n/launcher\n"
  },
  {
    "path": "go/cmd/updater/go.mod",
    "content": "module updater\n\ngo 1.18\n\nrequire github.com/codeclysm/extract v2.2.0+incompatible\n\nrequire (\n\tgithub.com/h2non/filetype v1.1.3 // indirect\n\tgithub.com/juju/errors v0.0.0-20220331221717-b38fca44723b // indirect\n\tgithub.com/stretchr/testify v1.7.1 // indirect\n)\n"
  },
  {
    "path": "go/cmd/updater/go.sum",
    "content": "github.com/codeclysm/extract v2.2.0+incompatible h1:q3wyckoA30bhUSiwdQezMqVhwd8+WGE64/GL//LtUhI=\ngithub.com/codeclysm/extract v2.2.0+incompatible/go.mod h1:2nhFMPHiU9At61hz+12bfrlpXSUrOnK+wR+KlGO4Uks=\ngithub.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=\ngithub.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=\ngithub.com/juju/errors v0.0.0-20220331221717-b38fca44723b h1:AxFeSQJfcm2O3ov1wqAkTKYFsnMw2g1B4PkYujfAdkY=\ngithub.com/juju/errors v0.0.0-20220331221717-b38fca44723b/go.mod h1:jMGj9DWF/qbo91ODcfJq6z/RYc3FX3taCBZMCcpI4Ls=\ngithub.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "go/cmd/updater/launcher.go",
    "content": "//go:build !linux\n\npackage main\n\nimport (\n\t\"log\"\n\t\"runtime\"\n)\n\nfunc Launcher() {\n\tlog.Fatalln(\"launcher is not for your platform\", runtime.GOOS)\n}\n"
  },
  {
    "path": "go/cmd/updater/launcher_linux.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n)\n\nvar local_qt_theme bool\n\nfunc Launcher() {\n\tlog.Println(\"Running as launcher\")\n\twd, _ := filepath.Abs(\".\")\n\n\t_debug := flag.Bool(\"debug\", false, \"Debug mode\")\n\tflag.Parse()\n\n\tcmd := exec.Command(\"./nekobox\", flag.Args()...)\n\n\tld_env := \"LD_LIBRARY_PATH=\" + filepath.Join(wd, \"./usr/lib\")\n\tqt_plugin_env := \"QT_PLUGIN_PATH=\" + filepath.Join(wd, \"./usr/plugins\")\n\n\t// Qt 5.12 abi is usually compatible with system Qt 5.15\n\t// But use package Qt 5.12 by default.\n\tcmd.Env = os.Environ()\n\tcmd.Env = append(cmd.Env, \"NKR_FROM_LAUNCHER=1\")\n\tcmd.Env = append(cmd.Env, ld_env, qt_plugin_env)\n\tlog.Println(ld_env, qt_plugin_env, cmd)\n\n\tif *_debug {\n\t\tcmd.Env = append(cmd.Env, \"QT_DEBUG_PLUGINS=1\")\n\t\tcmd.Stdin = os.Stdin\n\t\tcmd.Stderr = os.Stderr\n\t\tcmd.Stdout = os.Stdout\n\t\tcmd.Run()\n\t} else {\n\t\tcmd.Start()\n\t}\n}\n"
  },
  {
    "path": "go/cmd/updater/main.go",
    "content": "package main\n\nimport (\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc main() {\n\t// update & launcher\n\texe, err := os.Executable()\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\n\twd := filepath.Dir(exe)\n\tos.Chdir(wd)\n\texe = filepath.Base(os.Args[0])\n\tlog.Println(\"exe:\", exe, \"exe dir:\", wd)\n\n\tif strings.HasPrefix(strings.ToLower(exe), \"updater\") {\n\t\tif runtime.GOOS == \"windows\" {\n\t\t\tif strings.HasPrefix(strings.ToLower(exe), \"updater.old\") {\n\t\t\t\t// 2. \"updater.old\" update files\n\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\tUpdater()\n\t\t\t\t// 3. start\n\t\t\t\texec.Command(\"./nekobox.exe\").Start()\n\t\t\t} else {\n\t\t\t\t// 1. main prog quit and run \"updater.exe\"\n\t\t\t\tCopy(\"./updater.exe\", \"./updater.old\")\n\t\t\t\texec.Command(\"./updater.old\", os.Args[1:]...).Start()\n\t\t\t}\n\t\t} else {\n\t\t\t// 1. update files\n\t\t\tUpdater()\n\t\t\t// 2. start\n\t\t\tif os.Getenv(\"NKR_FROM_LAUNCHER\") == \"1\" {\n\t\t\t\tLauncher()\n\t\t\t} else {\n\t\t\t\texec.Command(\"./nekobox\").Start()\n\t\t\t}\n\t\t}\n\t\treturn\n\t} else if strings.HasPrefix(strings.ToLower(exe), \"launcher\") {\n\t\tLauncher()\n\t\treturn\n\t}\n\tlog.Fatalf(\"wrong name\")\n}\n\nfunc Copy(src string, dst string) {\n\t// Read all content of src to data\n\tdata, _ := ioutil.ReadFile(src)\n\t// Write data to dst\n\tioutil.WriteFile(dst, data, 0644)\n}\n"
  },
  {
    "path": "go/cmd/updater/msgbox.go",
    "content": "//go:build !windows\n\npackage main\n\nfunc MessageBoxPlain(title, caption string) int {\n\treturn 0\n}\n"
  },
  {
    "path": "go/cmd/updater/msgbox_windows.go",
    "content": "package main\n\nimport (\n\t\"syscall\"\n\t\"unsafe\"\n)\n\n// MessageBoxPlain of Win32 API.\nfunc MessageBoxPlain(title, caption string) int {\n\tconst (\n\t\tNULL  = 0\n\t\tMB_OK = 0\n\t)\n\treturn MessageBox(NULL, caption, title, MB_OK)\n}\n\n// MessageBox of Win32 API.\nfunc MessageBox(hwnd uintptr, caption, title string, flags uint) int {\n\tret, _, _ := syscall.NewLazyDLL(\"user32.dll\").NewProc(\"MessageBoxW\").Call(\n\t\tuintptr(hwnd),\n\t\tuintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))),\n\t\tuintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))),\n\t\tuintptr(flags))\n\n\treturn int(ret)\n}\n"
  },
  {
    "path": "go/cmd/updater/updater.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/codeclysm/extract\"\n)\n\nfunc Updater() {\n\tpre_cleanup := func() {\n\t\tif runtime.GOOS == \"linux\" {\n\t\t\tos.RemoveAll(\"./usr\")\n\t\t}\n\t\tos.RemoveAll(\"./nekoray_update\")\n\t}\n\n\t// find update package\n\tvar updatePackagePath string\n\tif len(os.Args) == 2 && Exist(os.Args[1]) {\n\t\tupdatePackagePath = os.Args[1]\n\t} else if Exist(\"./nekoray.zip\") {\n\t\tupdatePackagePath = \"./nekoray.zip\"\n\t} else if Exist(\"./nekoray.tar.gz\") {\n\t\tupdatePackagePath = \"./nekoray.tar.gz\"\n\t} else {\n\t\tlog.Fatalln(\"no update\")\n\t}\n\tlog.Println(\"updating from\", updatePackagePath)\n\n\t// extract update package\n\tif strings.HasSuffix(updatePackagePath, \".zip\") {\n\t\tpre_cleanup()\n\t\tf, err := os.Open(updatePackagePath)\n\t\tif err != nil {\n\t\t\tlog.Fatalln(err.Error())\n\t\t}\n\t\terr = extract.Zip(context.Background(), f, \"./nekoray_update\", nil)\n\t\tif err != nil {\n\t\t\tlog.Fatalln(err.Error())\n\t\t}\n\t\tf.Close()\n\t} else if strings.HasSuffix(updatePackagePath, \".tar.gz\") {\n\t\tpre_cleanup()\n\t\tf, err := os.Open(updatePackagePath)\n\t\tif err != nil {\n\t\t\tlog.Fatalln(err.Error())\n\t\t}\n\t\terr = extract.Gz(context.Background(), f, \"./nekoray_update\", nil)\n\t\tif err != nil {\n\t\t\tlog.Fatalln(err.Error())\n\t\t}\n\t\tf.Close()\n\t}\n\n\t// remove old file\n\tremoveAll(\"./*.dll\")\n\tremoveAll(\"./*.dmp\")\n\n\t// update move\n\terr := Mv(\"./nekoray_update/nekoray\", \"./\")\n\tif err != nil {\n\t\tMessageBoxPlain(\"NekoGui Updater\", \"Update failed. Please close the running instance and run the updater again.\\n\\n\"+err.Error())\n\t\tlog.Fatalln(err.Error())\n\t}\n\n\tos.RemoveAll(\"./nekoray_update\")\n\tos.RemoveAll(\"./nekoray.zip\")\n\tos.RemoveAll(\"./nekoray.tar.gz\")\n\n\t// nekoray -> nekobox\n\tos.Remove(\"./nekoray.exe\")\n\tos.Remove(\"./nekoray.png\")\n\tos.Remove(\"./nekoray_core.exe\")\n}\n\nfunc Exist(path string) bool {\n\t_, err := os.Stat(path)\n\treturn err == nil\n}\n\nfunc FindExist(paths []string) string {\n\tfor _, path := range paths {\n\t\tif Exist(path) {\n\t\t\treturn path\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc Mv(src, dst string) error {\n\ts, err := os.Stat(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif s.IsDir() {\n\t\tes, err := os.ReadDir(src)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, e := range es {\n\t\t\terr = Mv(filepath.Join(src, e.Name()), filepath.Join(dst, e.Name()))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t} else {\n\t\terr = os.MkdirAll(filepath.Dir(dst), 0755)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = os.Rename(src, dst)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc removeAll(glob string) {\n\tfiles, _ := filepath.Glob(glob)\n\tfor _, f := range files {\n\t\tos.Remove(f)\n\t}\n}\n"
  },
  {
    "path": "go/grpc_server/auth/auth.go",
    "content": "package auth\n\nimport (\n\t\"context\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// Authenticator exposes a function for authenticating requests.\ntype Authenticator struct {\n\tToken string\n}\n\n// Authenticate checks that a token exists and is valid. It stores the user\n// metadata in the returned context and removes the token from the context.\nfunc (a Authenticator) Authenticate(ctx context.Context) (newCtx context.Context, err error) {\n\tauth, err := extractHeader(ctx, \"nekoray_auth\")\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\n\tif auth != a.Token {\n\t\treturn ctx, status.Error(codes.Unauthenticated, \"invalid token\")\n\t}\n\n\treturn purgeHeader(ctx, \"nekoray_auth\"), nil\n}\n\nfunc extractHeader(ctx context.Context, header string) (string, error) {\n\tmd, ok := metadata.FromIncomingContext(ctx)\n\tif !ok {\n\t\treturn \"\", status.Error(codes.Unauthenticated, \"no headers in request\")\n\t}\n\n\tauthHeaders, ok := md[header]\n\tif !ok {\n\t\treturn \"\", status.Error(codes.Unauthenticated, \"no header in request\")\n\t}\n\n\tif len(authHeaders) != 1 {\n\t\treturn \"\", status.Error(codes.Unauthenticated, \"more than 1 header in request\")\n\t}\n\n\treturn authHeaders[0], nil\n}\n\nfunc purgeHeader(ctx context.Context, header string) context.Context {\n\tmd, _ := metadata.FromIncomingContext(ctx)\n\tmdCopy := md.Copy()\n\tmdCopy[header] = nil\n\treturn metadata.NewIncomingContext(ctx, mdCopy)\n}\n"
  },
  {
    "path": "go/grpc_server/fulltest.go",
    "content": "package grpc_server\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"grpc_server/gen\"\n\t\"io\"\n\t\"log\"\n\t\"math\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/matsuridayo/libneko/neko_common\"\n\t\"github.com/matsuridayo/libneko/speedtest\"\n)\n\nconst (\n\tKiB = 1024\n\tMiB = 1024 * KiB\n)\n\nfunc getBetweenStr(str, start, end string) string {\n\tn := strings.Index(str, start)\n\tif n == -1 {\n\t\tn = 0\n\t}\n\tstr = string([]byte(str)[n:])\n\tm := strings.Index(str, end)\n\tif m == -1 {\n\t\tm = len(str)\n\t}\n\tstr = string([]byte(str)[:m])\n\treturn str[len(start):]\n}\n\nfunc DoFullTest(ctx context.Context, in *gen.TestReq, instance interface{}) (out *gen.TestResp, _ error) {\n\tout = &gen.TestResp{}\n\thttpClient := neko_common.CreateProxyHttpClient(instance)\n\n\t// Latency\n\tvar latency string\n\tif in.FullLatency {\n\t\tt, _ := speedtest.UrlTest(httpClient, in.Url, in.Timeout, speedtest.UrlTestStandard_RTT)\n\t\tout.Ms = t\n\t\tif t > 0 {\n\t\t\tlatency = fmt.Sprint(t, \"ms\")\n\t\t} else {\n\t\t\tlatency = \"Error\"\n\t\t}\n\t}\n\n\t// UDP Latency\n\tvar udpLatency string\n\tif in.FullUdpLatency {\n\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second*3)\n\t\tresult := make(chan string)\n\n\t\tgo func() {\n\t\t\tvar startTime = time.Now()\n\t\t\tpc, err := neko_common.DialContext(ctx, instance, \"udp\", \"8.8.8.8:53\")\n\t\t\tif err == nil {\n\t\t\t\tdefer pc.Close()\n\t\t\t\tdnsPacket, _ := hex.DecodeString(\"0000010000010000000000000377777706676f6f676c6503636f6d0000010001\")\n\t\t\t\t_, err = pc.Write(dnsPacket)\n\t\t\t\tif err == nil {\n\t\t\t\t\tvar buf [1400]byte\n\t\t\t\t\t_, err = pc.Read(buf[:])\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\tvar endTime = time.Now()\n\t\t\t\tresult <- fmt.Sprint(endTime.Sub(startTime).Abs().Milliseconds(), \"ms\")\n\t\t\t} else {\n\t\t\t\tlog.Println(\"UDP Latency test error:\", err)\n\t\t\t\tresult <- \"Error\"\n\t\t\t}\n\t\t\tclose(result)\n\t\t}()\n\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tudpLatency = \"Timeout\"\n\t\tcase r := <-result:\n\t\t\tudpLatency = r\n\t\t}\n\t\tcancel()\n\t}\n\n\t// 入口 IP\n\tvar in_ip string\n\tif in.FullInOut {\n\t\t_in_ip, err := net.ResolveIPAddr(\"ip\", in.InAddress)\n\t\tif err == nil {\n\t\t\tin_ip = _in_ip.String()\n\t\t} else {\n\t\t\tin_ip = err.Error()\n\t\t}\n\t}\n\n\t// 出口 IP\n\tvar out_ip string\n\tif in.FullInOut {\n\t\tresp, err := httpClient.Get(\"https://www.cloudflare.com/cdn-cgi/trace\")\n\t\tif err == nil {\n\t\t\tb, _ := io.ReadAll(resp.Body)\n\t\t\tout_ip = getBetweenStr(string(b), \"ip=\", \"\\n\")\n\t\t\tresp.Body.Close()\n\t\t} else {\n\t\t\tout_ip = \"Error\"\n\t\t}\n\t}\n\n\t// 下载\n\tvar speed string\n\tif in.FullSpeed {\n\t\tif in.FullSpeedTimeout <= 0 {\n\t\t\tin.FullSpeedTimeout = 30\n\t\t}\n\n\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(in.FullSpeedTimeout))\n\t\tresult := make(chan string)\n\t\tvar bodyClose io.Closer\n\n\t\tgo func() {\n\t\t\treq, _ := http.NewRequestWithContext(ctx, \"GET\", in.FullSpeedUrl, nil)\n\t\t\tresp, err := httpClient.Do(req)\n\t\t\tif err == nil && resp != nil && resp.Body != nil {\n\t\t\t\tbodyClose = resp.Body\n\t\t\t\tdefer resp.Body.Close()\n\n\t\t\t\ttimeStart := time.Now()\n\t\t\t\tn, _ := io.Copy(io.Discard, resp.Body)\n\t\t\t\ttimeEnd := time.Now()\n\n\t\t\t\tduration := math.Max(timeEnd.Sub(timeStart).Seconds(), 0.000001)\n\t\t\t\tresultSpeed := (float64(n) / duration) / MiB\n\t\t\t\tresult <- fmt.Sprintf(\"%.2fMiB/s\", resultSpeed)\n\t\t\t} else {\n\t\t\t\tresult <- \"Error\"\n\t\t\t}\n\t\t\tclose(result)\n\t\t}()\n\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tspeed = \"Timeout\"\n\t\tcase s := <-result:\n\t\t\tspeed = s\n\t\t}\n\n\t\tcancel()\n\t\tif bodyClose != nil {\n\t\t\tbodyClose.Close()\n\t\t}\n\t}\n\n\tfr := make([]string, 0)\n\tif latency != \"\" {\n\t\tfr = append(fr, fmt.Sprintf(\"Latency: %s\", latency))\n\t}\n\tif udpLatency != \"\" {\n\t\tfr = append(fr, fmt.Sprintf(\"UDPLatency: %s\", udpLatency))\n\t}\n\tif speed != \"\" {\n\t\tfr = append(fr, fmt.Sprintf(\"Speed: %s\", speed))\n\t}\n\tif in_ip != \"\" {\n\t\tfr = append(fr, fmt.Sprintf(\"In: %s\", in_ip))\n\t}\n\tif out_ip != \"\" {\n\t\tfr = append(fr, fmt.Sprintf(\"Out: %s\", out_ip))\n\t}\n\n\tout.FullReport = strings.Join(fr, \" / \")\n\n\treturn\n}\n"
  },
  {
    "path": "go/grpc_server/gen/libcore.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.28.1\n// \tprotoc        v4.23.3\n// source: libcore.proto\n\npackage gen\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype TestMode int32\n\nconst (\n\tTestMode_TcpPing  TestMode = 0\n\tTestMode_UrlTest  TestMode = 1\n\tTestMode_FullTest TestMode = 2\n)\n\n// Enum value maps for TestMode.\nvar (\n\tTestMode_name = map[int32]string{\n\t\t0: \"TcpPing\",\n\t\t1: \"UrlTest\",\n\t\t2: \"FullTest\",\n\t}\n\tTestMode_value = map[string]int32{\n\t\t\"TcpPing\":  0,\n\t\t\"UrlTest\":  1,\n\t\t\"FullTest\": 2,\n\t}\n)\n\nfunc (x TestMode) Enum() *TestMode {\n\tp := new(TestMode)\n\t*p = x\n\treturn p\n}\n\nfunc (x TestMode) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (TestMode) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_libcore_proto_enumTypes[0].Descriptor()\n}\n\nfunc (TestMode) Type() protoreflect.EnumType {\n\treturn &file_libcore_proto_enumTypes[0]\n}\n\nfunc (x TestMode) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use TestMode.Descriptor instead.\nfunc (TestMode) EnumDescriptor() ([]byte, []int) {\n\treturn file_libcore_proto_rawDescGZIP(), []int{0}\n}\n\ntype UpdateAction int32\n\nconst (\n\tUpdateAction_Check    UpdateAction = 0\n\tUpdateAction_Download UpdateAction = 1\n)\n\n// Enum value maps for UpdateAction.\nvar (\n\tUpdateAction_name = map[int32]string{\n\t\t0: \"Check\",\n\t\t1: \"Download\",\n\t}\n\tUpdateAction_value = map[string]int32{\n\t\t\"Check\":    0,\n\t\t\"Download\": 1,\n\t}\n)\n\nfunc (x UpdateAction) Enum() *UpdateAction {\n\tp := new(UpdateAction)\n\t*p = x\n\treturn p\n}\n\nfunc (x UpdateAction) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (UpdateAction) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_libcore_proto_enumTypes[1].Descriptor()\n}\n\nfunc (UpdateAction) Type() protoreflect.EnumType {\n\treturn &file_libcore_proto_enumTypes[1]\n}\n\nfunc (x UpdateAction) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use UpdateAction.Descriptor instead.\nfunc (UpdateAction) EnumDescriptor() ([]byte, []int) {\n\treturn file_libcore_proto_rawDescGZIP(), []int{1}\n}\n\ntype EmptyReq struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *EmptyReq) Reset() {\n\t*x = EmptyReq{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_libcore_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *EmptyReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EmptyReq) ProtoMessage() {}\n\nfunc (x *EmptyReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_libcore_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use EmptyReq.ProtoReflect.Descriptor instead.\nfunc (*EmptyReq) Descriptor() ([]byte, []int) {\n\treturn file_libcore_proto_rawDescGZIP(), []int{0}\n}\n\ntype EmptyResp struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *EmptyResp) Reset() {\n\t*x = EmptyResp{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_libcore_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *EmptyResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EmptyResp) ProtoMessage() {}\n\nfunc (x *EmptyResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_libcore_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use EmptyResp.ProtoReflect.Descriptor instead.\nfunc (*EmptyResp) Descriptor() ([]byte, []int) {\n\treturn file_libcore_proto_rawDescGZIP(), []int{1}\n}\n\ntype ErrorResp struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tError string `protobuf:\"bytes,1,opt,name=error,proto3\" json:\"error,omitempty\"`\n}\n\nfunc (x *ErrorResp) Reset() {\n\t*x = ErrorResp{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_libcore_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ErrorResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ErrorResp) ProtoMessage() {}\n\nfunc (x *ErrorResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_libcore_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ErrorResp.ProtoReflect.Descriptor instead.\nfunc (*ErrorResp) Descriptor() ([]byte, []int) {\n\treturn file_libcore_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *ErrorResp) GetError() string {\n\tif x != nil {\n\t\treturn x.Error\n\t}\n\treturn \"\"\n}\n\ntype LoadConfigReq struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCoreConfig               string   `protobuf:\"bytes,1,opt,name=core_config,json=coreConfig,proto3\" json:\"core_config,omitempty\"`\n\tEnableNekorayConnections bool     `protobuf:\"varint,2,opt,name=enable_nekoray_connections,json=enableNekorayConnections,proto3\" json:\"enable_nekoray_connections,omitempty\"`\n\tStatsOutbounds           []string `protobuf:\"bytes,3,rep,name=stats_outbounds,json=statsOutbounds,proto3\" json:\"stats_outbounds,omitempty\"`\n}\n\nfunc (x *LoadConfigReq) Reset() {\n\t*x = LoadConfigReq{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_libcore_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *LoadConfigReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LoadConfigReq) ProtoMessage() {}\n\nfunc (x *LoadConfigReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_libcore_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LoadConfigReq.ProtoReflect.Descriptor instead.\nfunc (*LoadConfigReq) Descriptor() ([]byte, []int) {\n\treturn file_libcore_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *LoadConfigReq) GetCoreConfig() string {\n\tif x != nil {\n\t\treturn x.CoreConfig\n\t}\n\treturn \"\"\n}\n\nfunc (x *LoadConfigReq) GetEnableNekorayConnections() bool {\n\tif x != nil {\n\t\treturn x.EnableNekorayConnections\n\t}\n\treturn false\n}\n\nfunc (x *LoadConfigReq) GetStatsOutbounds() []string {\n\tif x != nil {\n\t\treturn x.StatsOutbounds\n\t}\n\treturn nil\n}\n\ntype TestReq struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tMode    TestMode `protobuf:\"varint,1,opt,name=mode,proto3,enum=libcore.TestMode\" json:\"mode,omitempty\"`\n\tTimeout int32    `protobuf:\"varint,6,opt,name=timeout,proto3\" json:\"timeout,omitempty\"`\n\t// TcpPing\n\tAddress string `protobuf:\"bytes,2,opt,name=address,proto3\" json:\"address,omitempty\"`\n\t// UrlTest\n\tConfig  *LoadConfigReq `protobuf:\"bytes,3,opt,name=config,proto3\" json:\"config,omitempty\"`\n\tInbound string         `protobuf:\"bytes,4,opt,name=inbound,proto3\" json:\"inbound,omitempty\"`\n\tUrl     string         `protobuf:\"bytes,5,opt,name=url,proto3\" json:\"url,omitempty\"`\n\t// FullTest\n\tInAddress        string `protobuf:\"bytes,7,opt,name=in_address,json=inAddress,proto3\" json:\"in_address,omitempty\"`\n\tFullLatency      bool   `protobuf:\"varint,8,opt,name=full_latency,json=fullLatency,proto3\" json:\"full_latency,omitempty\"`\n\tFullSpeed        bool   `protobuf:\"varint,9,opt,name=full_speed,json=fullSpeed,proto3\" json:\"full_speed,omitempty\"`\n\tFullSpeedUrl     string `protobuf:\"bytes,13,opt,name=full_speed_url,json=fullSpeedUrl,proto3\" json:\"full_speed_url,omitempty\"`\n\tFullSpeedTimeout int32  `protobuf:\"varint,14,opt,name=full_speed_timeout,json=fullSpeedTimeout,proto3\" json:\"full_speed_timeout,omitempty\"`\n\tFullInOut        bool   `protobuf:\"varint,10,opt,name=full_in_out,json=fullInOut,proto3\" json:\"full_in_out,omitempty\"`\n\tFullUdpLatency   bool   `protobuf:\"varint,12,opt,name=full_udp_latency,json=fullUdpLatency,proto3\" json:\"full_udp_latency,omitempty\"`\n\t// Deprecated: Do not use.\n\tFullNat bool `protobuf:\"varint,11,opt,name=full_nat,json=fullNat,proto3\" json:\"full_nat,omitempty\"`\n}\n\nfunc (x *TestReq) Reset() {\n\t*x = TestReq{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_libcore_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TestReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TestReq) ProtoMessage() {}\n\nfunc (x *TestReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_libcore_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TestReq.ProtoReflect.Descriptor instead.\nfunc (*TestReq) Descriptor() ([]byte, []int) {\n\treturn file_libcore_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *TestReq) GetMode() TestMode {\n\tif x != nil {\n\t\treturn x.Mode\n\t}\n\treturn TestMode_TcpPing\n}\n\nfunc (x *TestReq) GetTimeout() int32 {\n\tif x != nil {\n\t\treturn x.Timeout\n\t}\n\treturn 0\n}\n\nfunc (x *TestReq) GetAddress() string {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn \"\"\n}\n\nfunc (x *TestReq) GetConfig() *LoadConfigReq {\n\tif x != nil {\n\t\treturn x.Config\n\t}\n\treturn nil\n}\n\nfunc (x *TestReq) GetInbound() string {\n\tif x != nil {\n\t\treturn x.Inbound\n\t}\n\treturn \"\"\n}\n\nfunc (x *TestReq) GetUrl() string {\n\tif x != nil {\n\t\treturn x.Url\n\t}\n\treturn \"\"\n}\n\nfunc (x *TestReq) GetInAddress() string {\n\tif x != nil {\n\t\treturn x.InAddress\n\t}\n\treturn \"\"\n}\n\nfunc (x *TestReq) GetFullLatency() bool {\n\tif x != nil {\n\t\treturn x.FullLatency\n\t}\n\treturn false\n}\n\nfunc (x *TestReq) GetFullSpeed() bool {\n\tif x != nil {\n\t\treturn x.FullSpeed\n\t}\n\treturn false\n}\n\nfunc (x *TestReq) GetFullSpeedUrl() string {\n\tif x != nil {\n\t\treturn x.FullSpeedUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *TestReq) GetFullSpeedTimeout() int32 {\n\tif x != nil {\n\t\treturn x.FullSpeedTimeout\n\t}\n\treturn 0\n}\n\nfunc (x *TestReq) GetFullInOut() bool {\n\tif x != nil {\n\t\treturn x.FullInOut\n\t}\n\treturn false\n}\n\nfunc (x *TestReq) GetFullUdpLatency() bool {\n\tif x != nil {\n\t\treturn x.FullUdpLatency\n\t}\n\treturn false\n}\n\n// Deprecated: Do not use.\nfunc (x *TestReq) GetFullNat() bool {\n\tif x != nil {\n\t\treturn x.FullNat\n\t}\n\treturn false\n}\n\ntype TestResp struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tError      string `protobuf:\"bytes,1,opt,name=error,proto3\" json:\"error,omitempty\"`\n\tMs         int32  `protobuf:\"varint,2,opt,name=ms,proto3\" json:\"ms,omitempty\"`\n\tFullReport string `protobuf:\"bytes,3,opt,name=full_report,json=fullReport,proto3\" json:\"full_report,omitempty\"`\n}\n\nfunc (x *TestResp) Reset() {\n\t*x = TestResp{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_libcore_proto_msgTypes[5]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TestResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TestResp) ProtoMessage() {}\n\nfunc (x *TestResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_libcore_proto_msgTypes[5]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TestResp.ProtoReflect.Descriptor instead.\nfunc (*TestResp) Descriptor() ([]byte, []int) {\n\treturn file_libcore_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *TestResp) GetError() string {\n\tif x != nil {\n\t\treturn x.Error\n\t}\n\treturn \"\"\n}\n\nfunc (x *TestResp) GetMs() int32 {\n\tif x != nil {\n\t\treturn x.Ms\n\t}\n\treturn 0\n}\n\nfunc (x *TestResp) GetFullReport() string {\n\tif x != nil {\n\t\treturn x.FullReport\n\t}\n\treturn \"\"\n}\n\ntype QueryStatsReq struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTag    string `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tDirect string `protobuf:\"bytes,2,opt,name=direct,proto3\" json:\"direct,omitempty\"`\n}\n\nfunc (x *QueryStatsReq) Reset() {\n\t*x = QueryStatsReq{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_libcore_proto_msgTypes[6]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *QueryStatsReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*QueryStatsReq) ProtoMessage() {}\n\nfunc (x *QueryStatsReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_libcore_proto_msgTypes[6]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use QueryStatsReq.ProtoReflect.Descriptor instead.\nfunc (*QueryStatsReq) Descriptor() ([]byte, []int) {\n\treturn file_libcore_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *QueryStatsReq) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *QueryStatsReq) GetDirect() string {\n\tif x != nil {\n\t\treturn x.Direct\n\t}\n\treturn \"\"\n}\n\ntype QueryStatsResp struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTraffic int64 `protobuf:\"varint,1,opt,name=traffic,proto3\" json:\"traffic,omitempty\"`\n}\n\nfunc (x *QueryStatsResp) Reset() {\n\t*x = QueryStatsResp{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_libcore_proto_msgTypes[7]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *QueryStatsResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*QueryStatsResp) ProtoMessage() {}\n\nfunc (x *QueryStatsResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_libcore_proto_msgTypes[7]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use QueryStatsResp.ProtoReflect.Descriptor instead.\nfunc (*QueryStatsResp) Descriptor() ([]byte, []int) {\n\treturn file_libcore_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *QueryStatsResp) GetTraffic() int64 {\n\tif x != nil {\n\t\treturn x.Traffic\n\t}\n\treturn 0\n}\n\ntype UpdateReq struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAction          UpdateAction `protobuf:\"varint,1,opt,name=action,proto3,enum=libcore.UpdateAction\" json:\"action,omitempty\"`\n\tCheckPreRelease bool         `protobuf:\"varint,2,opt,name=check_pre_release,json=checkPreRelease,proto3\" json:\"check_pre_release,omitempty\"`\n}\n\nfunc (x *UpdateReq) Reset() {\n\t*x = UpdateReq{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_libcore_proto_msgTypes[8]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UpdateReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdateReq) ProtoMessage() {}\n\nfunc (x *UpdateReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_libcore_proto_msgTypes[8]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdateReq.ProtoReflect.Descriptor instead.\nfunc (*UpdateReq) Descriptor() ([]byte, []int) {\n\treturn file_libcore_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *UpdateReq) GetAction() UpdateAction {\n\tif x != nil {\n\t\treturn x.Action\n\t}\n\treturn UpdateAction_Check\n}\n\nfunc (x *UpdateReq) GetCheckPreRelease() bool {\n\tif x != nil {\n\t\treturn x.CheckPreRelease\n\t}\n\treturn false\n}\n\ntype UpdateResp struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tError        string `protobuf:\"bytes,1,opt,name=error,proto3\" json:\"error,omitempty\"`\n\tAssetsName   string `protobuf:\"bytes,2,opt,name=assets_name,json=assetsName,proto3\" json:\"assets_name,omitempty\"`\n\tDownloadUrl  string `protobuf:\"bytes,3,opt,name=download_url,json=downloadUrl,proto3\" json:\"download_url,omitempty\"`\n\tReleaseUrl   string `protobuf:\"bytes,4,opt,name=release_url,json=releaseUrl,proto3\" json:\"release_url,omitempty\"`\n\tReleaseNote  string `protobuf:\"bytes,5,opt,name=release_note,json=releaseNote,proto3\" json:\"release_note,omitempty\"`\n\tIsPreRelease bool   `protobuf:\"varint,6,opt,name=is_pre_release,json=isPreRelease,proto3\" json:\"is_pre_release,omitempty\"`\n}\n\nfunc (x *UpdateResp) Reset() {\n\t*x = UpdateResp{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_libcore_proto_msgTypes[9]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UpdateResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdateResp) ProtoMessage() {}\n\nfunc (x *UpdateResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_libcore_proto_msgTypes[9]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdateResp.ProtoReflect.Descriptor instead.\nfunc (*UpdateResp) Descriptor() ([]byte, []int) {\n\treturn file_libcore_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *UpdateResp) GetError() string {\n\tif x != nil {\n\t\treturn x.Error\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateResp) GetAssetsName() string {\n\tif x != nil {\n\t\treturn x.AssetsName\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateResp) GetDownloadUrl() string {\n\tif x != nil {\n\t\treturn x.DownloadUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateResp) GetReleaseUrl() string {\n\tif x != nil {\n\t\treturn x.ReleaseUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateResp) GetReleaseNote() string {\n\tif x != nil {\n\t\treturn x.ReleaseNote\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateResp) GetIsPreRelease() bool {\n\tif x != nil {\n\t\treturn x.IsPreRelease\n\t}\n\treturn false\n}\n\ntype ListConnectionsResp struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tNekorayConnectionsJson string `protobuf:\"bytes,1,opt,name=nekoray_connections_json,json=nekorayConnectionsJson,proto3\" json:\"nekoray_connections_json,omitempty\"`\n}\n\nfunc (x *ListConnectionsResp) Reset() {\n\t*x = ListConnectionsResp{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_libcore_proto_msgTypes[10]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListConnectionsResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListConnectionsResp) ProtoMessage() {}\n\nfunc (x *ListConnectionsResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_libcore_proto_msgTypes[10]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListConnectionsResp.ProtoReflect.Descriptor instead.\nfunc (*ListConnectionsResp) Descriptor() ([]byte, []int) {\n\treturn file_libcore_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *ListConnectionsResp) GetNekorayConnectionsJson() string {\n\tif x != nil {\n\t\treturn x.NekorayConnectionsJson\n\t}\n\treturn \"\"\n}\n\nvar File_libcore_proto protoreflect.FileDescriptor\n\nvar file_libcore_proto_rawDesc = []byte{\n\t0x0a, 0x0d, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,\n\t0x07, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x0a, 0x0a, 0x08, 0x45, 0x6d, 0x70, 0x74,\n\t0x79, 0x52, 0x65, 0x71, 0x22, 0x0b, 0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73,\n\t0x70, 0x22, 0x21, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14,\n\t0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65,\n\t0x72, 0x72, 0x6f, 0x72, 0x22, 0x97, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e,\n\t0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x63,\n\t0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x72,\n\t0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x6e, 0x61, 0x62, 0x6c,\n\t0x65, 0x5f, 0x6e, 0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x65, 0x6e, 0x61,\n\t0x62, 0x6c, 0x65, 0x4e, 0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x6f,\n\t0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e,\n\t0x73, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x22, 0xde,\n\t0x03, 0x0a, 0x07, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x12, 0x25, 0x0a, 0x04, 0x6d, 0x6f,\n\t0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f,\n\t0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64,\n\t0x65, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01,\n\t0x28, 0x05, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61,\n\t0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64,\n\t0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18,\n\t0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e,\n\t0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x52, 0x06, 0x63,\n\t0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,\n\t0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12,\n\t0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72,\n\t0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18,\n\t0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,\n\t0x12, 0x21, 0x0a, 0x0c, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79,\n\t0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x66, 0x75, 0x6c, 0x6c, 0x4c, 0x61, 0x74, 0x65,\n\t0x6e, 0x63, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x65,\n\t0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x70, 0x65,\n\t0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64,\n\t0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x75, 0x6c, 0x6c,\n\t0x53, 0x70, 0x65, 0x65, 0x64, 0x55, 0x72, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x66, 0x75, 0x6c, 0x6c,\n\t0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x0e,\n\t0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54,\n\t0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x69,\n\t0x6e, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x66, 0x75, 0x6c,\n\t0x6c, 0x49, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x75,\n\t0x64, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08,\n\t0x52, 0x0e, 0x66, 0x75, 0x6c, 0x6c, 0x55, 0x64, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79,\n\t0x12, 0x1d, 0x0a, 0x08, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01,\n\t0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x66, 0x75, 0x6c, 0x6c, 0x4e, 0x61, 0x74, 0x22,\n\t0x51, 0x0a, 0x08, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x65,\n\t0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,\n\t0x72, 0x12, 0x0e, 0x0a, 0x02, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x6d,\n\t0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74,\n\t0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x52, 0x65, 0x70, 0x6f,\n\t0x72, 0x74, 0x22, 0x39, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73,\n\t0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0x2a, 0x0a,\n\t0x0e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12,\n\t0x18, 0x0a, 0x07, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,\n\t0x52, 0x07, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x22, 0x66, 0x0a, 0x09, 0x55, 0x70, 0x64,\n\t0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65,\n\t0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61,\n\t0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x70,\n\t0x72, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,\n\t0x52, 0x0f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x72, 0x65, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73,\n\t0x65, 0x22, 0xd0, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70,\n\t0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73,\n\t0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x73, 0x73,\n\t0x65, 0x74, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x77, 0x6e, 0x6c,\n\t0x6f, 0x61, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64,\n\t0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65,\n\t0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x0a, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x72,\n\t0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x0b, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x12, 0x24,\n\t0x0a, 0x0e, 0x69, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65,\n\t0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x73, 0x50, 0x72, 0x65, 0x52, 0x65, 0x6c,\n\t0x65, 0x61, 0x73, 0x65, 0x22, 0x4f, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e,\n\t0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x38, 0x0a, 0x18, 0x6e,\n\t0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x6e,\n\t0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,\n\t0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x2a, 0x32, 0x0a, 0x08, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x6f, 0x64,\n\t0x65, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x63, 0x70, 0x50, 0x69, 0x6e, 0x67, 0x10, 0x00, 0x12, 0x0b,\n\t0x0a, 0x07, 0x55, 0x72, 0x6c, 0x54, 0x65, 0x73, 0x74, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x46,\n\t0x75, 0x6c, 0x6c, 0x54, 0x65, 0x73, 0x74, 0x10, 0x02, 0x2a, 0x27, 0x0a, 0x0c, 0x55, 0x70, 0x64,\n\t0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x68, 0x65,\n\t0x63, 0x6b, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64,\n\t0x10, 0x01, 0x32, 0x94, 0x03, 0x0a, 0x0e, 0x4c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x45, 0x78, 0x69, 0x74, 0x12, 0x11, 0x2e,\n\t0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71,\n\t0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,\n\t0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,\n\t0x12, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74,\n\t0x65, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55,\n\t0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x05, 0x53,\n\t0x74, 0x61, 0x72, 0x74, 0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c,\n\t0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c,\n\t0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70,\n\t0x22, 0x00, 0x12, 0x2f, 0x0a, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62,\n\t0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e,\n\t0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73,\n\t0x70, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x10, 0x2e, 0x6c, 0x69,\n\t0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e,\n\t0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70,\n\t0x22, 0x00, 0x12, 0x3f, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73,\n\t0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79,\n\t0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f,\n\t0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73,\n\t0x70, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65,\n\t0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65,\n\t0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63,\n\t0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x11, 0x5a, 0x0f, 0x67, 0x72, 0x70,\n\t0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_libcore_proto_rawDescOnce sync.Once\n\tfile_libcore_proto_rawDescData = file_libcore_proto_rawDesc\n)\n\nfunc file_libcore_proto_rawDescGZIP() []byte {\n\tfile_libcore_proto_rawDescOnce.Do(func() {\n\t\tfile_libcore_proto_rawDescData = protoimpl.X.CompressGZIP(file_libcore_proto_rawDescData)\n\t})\n\treturn file_libcore_proto_rawDescData\n}\n\nvar file_libcore_proto_enumTypes = make([]protoimpl.EnumInfo, 2)\nvar file_libcore_proto_msgTypes = make([]protoimpl.MessageInfo, 11)\nvar file_libcore_proto_goTypes = []interface{}{\n\t(TestMode)(0),               // 0: libcore.TestMode\n\t(UpdateAction)(0),           // 1: libcore.UpdateAction\n\t(*EmptyReq)(nil),            // 2: libcore.EmptyReq\n\t(*EmptyResp)(nil),           // 3: libcore.EmptyResp\n\t(*ErrorResp)(nil),           // 4: libcore.ErrorResp\n\t(*LoadConfigReq)(nil),       // 5: libcore.LoadConfigReq\n\t(*TestReq)(nil),             // 6: libcore.TestReq\n\t(*TestResp)(nil),            // 7: libcore.TestResp\n\t(*QueryStatsReq)(nil),       // 8: libcore.QueryStatsReq\n\t(*QueryStatsResp)(nil),      // 9: libcore.QueryStatsResp\n\t(*UpdateReq)(nil),           // 10: libcore.UpdateReq\n\t(*UpdateResp)(nil),          // 11: libcore.UpdateResp\n\t(*ListConnectionsResp)(nil), // 12: libcore.ListConnectionsResp\n}\nvar file_libcore_proto_depIdxs = []int32{\n\t0,  // 0: libcore.TestReq.mode:type_name -> libcore.TestMode\n\t5,  // 1: libcore.TestReq.config:type_name -> libcore.LoadConfigReq\n\t1,  // 2: libcore.UpdateReq.action:type_name -> libcore.UpdateAction\n\t2,  // 3: libcore.LibcoreService.Exit:input_type -> libcore.EmptyReq\n\t10, // 4: libcore.LibcoreService.Update:input_type -> libcore.UpdateReq\n\t5,  // 5: libcore.LibcoreService.Start:input_type -> libcore.LoadConfigReq\n\t2,  // 6: libcore.LibcoreService.Stop:input_type -> libcore.EmptyReq\n\t6,  // 7: libcore.LibcoreService.Test:input_type -> libcore.TestReq\n\t8,  // 8: libcore.LibcoreService.QueryStats:input_type -> libcore.QueryStatsReq\n\t2,  // 9: libcore.LibcoreService.ListConnections:input_type -> libcore.EmptyReq\n\t3,  // 10: libcore.LibcoreService.Exit:output_type -> libcore.EmptyResp\n\t11, // 11: libcore.LibcoreService.Update:output_type -> libcore.UpdateResp\n\t4,  // 12: libcore.LibcoreService.Start:output_type -> libcore.ErrorResp\n\t4,  // 13: libcore.LibcoreService.Stop:output_type -> libcore.ErrorResp\n\t7,  // 14: libcore.LibcoreService.Test:output_type -> libcore.TestResp\n\t9,  // 15: libcore.LibcoreService.QueryStats:output_type -> libcore.QueryStatsResp\n\t12, // 16: libcore.LibcoreService.ListConnections:output_type -> libcore.ListConnectionsResp\n\t10, // [10:17] is the sub-list for method output_type\n\t3,  // [3:10] is the sub-list for method input_type\n\t3,  // [3:3] is the sub-list for extension type_name\n\t3,  // [3:3] is the sub-list for extension extendee\n\t0,  // [0:3] is the sub-list for field type_name\n}\n\nfunc init() { file_libcore_proto_init() }\nfunc file_libcore_proto_init() {\n\tif File_libcore_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_libcore_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*EmptyReq); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_libcore_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*EmptyResp); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_libcore_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ErrorResp); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_libcore_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*LoadConfigReq); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_libcore_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TestReq); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_libcore_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TestResp); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_libcore_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*QueryStatsReq); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_libcore_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*QueryStatsResp); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_libcore_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UpdateReq); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_libcore_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UpdateResp); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_libcore_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ListConnectionsResp); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_libcore_proto_rawDesc,\n\t\t\tNumEnums:      2,\n\t\t\tNumMessages:   11,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_libcore_proto_goTypes,\n\t\tDependencyIndexes: file_libcore_proto_depIdxs,\n\t\tEnumInfos:         file_libcore_proto_enumTypes,\n\t\tMessageInfos:      file_libcore_proto_msgTypes,\n\t}.Build()\n\tFile_libcore_proto = out.File\n\tfile_libcore_proto_rawDesc = nil\n\tfile_libcore_proto_goTypes = nil\n\tfile_libcore_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "go/grpc_server/gen/libcore.proto",
    "content": "syntax = \"proto3\";\n\npackage libcore;\noption go_package = \"grpc_server/gen\";\n\nservice LibcoreService {\n  rpc Exit(EmptyReq) returns (EmptyResp) {}\n  rpc Update(UpdateReq) returns (UpdateResp) {}\n  //\n  rpc Start(LoadConfigReq) returns (ErrorResp) {}\n  rpc Stop(EmptyReq) returns (ErrorResp) {}\n  rpc Test(TestReq) returns (TestResp) {}\n  rpc QueryStats(QueryStatsReq) returns (QueryStatsResp) {}\n  rpc ListConnections(EmptyReq) returns (ListConnectionsResp) {}\n}\n\nmessage EmptyReq {}\n\nmessage EmptyResp {}\n\nmessage ErrorResp {\n  string error = 1;\n}\n\nmessage LoadConfigReq {\n  string core_config = 1;\n  bool enable_nekoray_connections = 2;\n  repeated string stats_outbounds = 3;\n}\n\nenum TestMode {\n  TcpPing = 0;\n  UrlTest = 1;\n  FullTest = 2;\n}\n\nmessage TestReq {\n  TestMode mode = 1;\n  int32 timeout = 6;\n  // TcpPing\n  string address = 2;\n  // UrlTest\n  LoadConfigReq config = 3;\n  string inbound = 4;\n  string url = 5;\n  // FullTest\n  string in_address = 7;\n  bool full_latency = 8;\n  bool full_speed = 9;\n  string full_speed_url = 13;\n  int32 full_speed_timeout = 14;\n  bool full_in_out = 10;\n  bool full_udp_latency = 12;\n  //\n  bool full_nat = 11 [deprecated = true];\n}\n\nmessage TestResp {\n  string error = 1;\n  int32 ms = 2;\n  string full_report = 3;\n}\n\nmessage QueryStatsReq{\n  string tag = 1;\n  string direct = 2;\n}\n\nmessage QueryStatsResp{\n  int64 traffic = 1;\n}\n\nenum UpdateAction {\n  Check = 0;\n  Download = 1;\n}\n\nmessage UpdateReq {\n  UpdateAction action = 1;\n  bool check_pre_release = 2;\n}\n\nmessage UpdateResp {\n  string error = 1;\n  string assets_name = 2;\n  string download_url = 3;\n  string release_url = 4;\n  string release_note = 5;\n  bool is_pre_release = 6;\n}\n\nmessage ListConnectionsResp {\n  string nekoray_connections_json = 1;\n}\n"
  },
  {
    "path": "go/grpc_server/gen/libcore_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.2.0\n// - protoc             v4.23.3\n// source: libcore.proto\n\npackage gen\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.32.0 or later.\nconst _ = grpc.SupportPackageIsVersion7\n\n// LibcoreServiceClient is the client API for LibcoreService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype LibcoreServiceClient interface {\n\tExit(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error)\n\tUpdate(ctx context.Context, in *UpdateReq, opts ...grpc.CallOption) (*UpdateResp, error)\n\tStart(ctx context.Context, in *LoadConfigReq, opts ...grpc.CallOption) (*ErrorResp, error)\n\tStop(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ErrorResp, error)\n\tTest(ctx context.Context, in *TestReq, opts ...grpc.CallOption) (*TestResp, error)\n\tQueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error)\n\tListConnections(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ListConnectionsResp, error)\n}\n\ntype libcoreServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewLibcoreServiceClient(cc grpc.ClientConnInterface) LibcoreServiceClient {\n\treturn &libcoreServiceClient{cc}\n}\n\nfunc (c *libcoreServiceClient) Exit(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error) {\n\tout := new(EmptyResp)\n\terr := c.cc.Invoke(ctx, \"/libcore.LibcoreService/Exit\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *libcoreServiceClient) Update(ctx context.Context, in *UpdateReq, opts ...grpc.CallOption) (*UpdateResp, error) {\n\tout := new(UpdateResp)\n\terr := c.cc.Invoke(ctx, \"/libcore.LibcoreService/Update\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *libcoreServiceClient) Start(ctx context.Context, in *LoadConfigReq, opts ...grpc.CallOption) (*ErrorResp, error) {\n\tout := new(ErrorResp)\n\terr := c.cc.Invoke(ctx, \"/libcore.LibcoreService/Start\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *libcoreServiceClient) Stop(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ErrorResp, error) {\n\tout := new(ErrorResp)\n\terr := c.cc.Invoke(ctx, \"/libcore.LibcoreService/Stop\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *libcoreServiceClient) Test(ctx context.Context, in *TestReq, opts ...grpc.CallOption) (*TestResp, error) {\n\tout := new(TestResp)\n\terr := c.cc.Invoke(ctx, \"/libcore.LibcoreService/Test\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *libcoreServiceClient) QueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error) {\n\tout := new(QueryStatsResp)\n\terr := c.cc.Invoke(ctx, \"/libcore.LibcoreService/QueryStats\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *libcoreServiceClient) ListConnections(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ListConnectionsResp, error) {\n\tout := new(ListConnectionsResp)\n\terr := c.cc.Invoke(ctx, \"/libcore.LibcoreService/ListConnections\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// LibcoreServiceServer is the server API for LibcoreService service.\n// All implementations must embed UnimplementedLibcoreServiceServer\n// for forward compatibility\ntype LibcoreServiceServer interface {\n\tExit(context.Context, *EmptyReq) (*EmptyResp, error)\n\tUpdate(context.Context, *UpdateReq) (*UpdateResp, error)\n\tStart(context.Context, *LoadConfigReq) (*ErrorResp, error)\n\tStop(context.Context, *EmptyReq) (*ErrorResp, error)\n\tTest(context.Context, *TestReq) (*TestResp, error)\n\tQueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error)\n\tListConnections(context.Context, *EmptyReq) (*ListConnectionsResp, error)\n\tmustEmbedUnimplementedLibcoreServiceServer()\n}\n\n// UnimplementedLibcoreServiceServer must be embedded to have forward compatible implementations.\ntype UnimplementedLibcoreServiceServer struct {\n}\n\nfunc (UnimplementedLibcoreServiceServer) Exit(context.Context, *EmptyReq) (*EmptyResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Exit not implemented\")\n}\nfunc (UnimplementedLibcoreServiceServer) Update(context.Context, *UpdateReq) (*UpdateResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Update not implemented\")\n}\nfunc (UnimplementedLibcoreServiceServer) Start(context.Context, *LoadConfigReq) (*ErrorResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Start not implemented\")\n}\nfunc (UnimplementedLibcoreServiceServer) Stop(context.Context, *EmptyReq) (*ErrorResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Stop not implemented\")\n}\nfunc (UnimplementedLibcoreServiceServer) Test(context.Context, *TestReq) (*TestResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Test not implemented\")\n}\nfunc (UnimplementedLibcoreServiceServer) QueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method QueryStats not implemented\")\n}\nfunc (UnimplementedLibcoreServiceServer) ListConnections(context.Context, *EmptyReq) (*ListConnectionsResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListConnections not implemented\")\n}\nfunc (UnimplementedLibcoreServiceServer) mustEmbedUnimplementedLibcoreServiceServer() {}\n\n// UnsafeLibcoreServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to LibcoreServiceServer will\n// result in compilation errors.\ntype UnsafeLibcoreServiceServer interface {\n\tmustEmbedUnimplementedLibcoreServiceServer()\n}\n\nfunc RegisterLibcoreServiceServer(s grpc.ServiceRegistrar, srv LibcoreServiceServer) {\n\ts.RegisterService(&LibcoreService_ServiceDesc, srv)\n}\n\nfunc _LibcoreService_Exit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(EmptyReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LibcoreServiceServer).Exit(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/libcore.LibcoreService/Exit\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LibcoreServiceServer).Exit(ctx, req.(*EmptyReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _LibcoreService_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(UpdateReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LibcoreServiceServer).Update(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/libcore.LibcoreService/Update\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LibcoreServiceServer).Update(ctx, req.(*UpdateReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _LibcoreService_Start_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(LoadConfigReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LibcoreServiceServer).Start(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/libcore.LibcoreService/Start\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LibcoreServiceServer).Start(ctx, req.(*LoadConfigReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _LibcoreService_Stop_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(EmptyReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LibcoreServiceServer).Stop(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/libcore.LibcoreService/Stop\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LibcoreServiceServer).Stop(ctx, req.(*EmptyReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _LibcoreService_Test_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(TestReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LibcoreServiceServer).Test(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/libcore.LibcoreService/Test\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LibcoreServiceServer).Test(ctx, req.(*TestReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _LibcoreService_QueryStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(QueryStatsReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LibcoreServiceServer).QueryStats(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/libcore.LibcoreService/QueryStats\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LibcoreServiceServer).QueryStats(ctx, req.(*QueryStatsReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _LibcoreService_ListConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(EmptyReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LibcoreServiceServer).ListConnections(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/libcore.LibcoreService/ListConnections\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LibcoreServiceServer).ListConnections(ctx, req.(*EmptyReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// LibcoreService_ServiceDesc is the grpc.ServiceDesc for LibcoreService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar LibcoreService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"libcore.LibcoreService\",\n\tHandlerType: (*LibcoreServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Exit\",\n\t\t\tHandler:    _LibcoreService_Exit_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Update\",\n\t\t\tHandler:    _LibcoreService_Update_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Start\",\n\t\t\tHandler:    _LibcoreService_Start_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Stop\",\n\t\t\tHandler:    _LibcoreService_Stop_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Test\",\n\t\t\tHandler:    _LibcoreService_Test_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"QueryStats\",\n\t\t\tHandler:    _LibcoreService_QueryStats_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ListConnections\",\n\t\t\tHandler:    _LibcoreService_ListConnections_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"libcore.proto\",\n}\n"
  },
  {
    "path": "go/grpc_server/gen/update_proto.sh",
    "content": "protoc -I . --go_out=. --go_opt paths=source_relative --go-grpc_out=. --go-grpc_opt paths=source_relative libcore.proto\n\n# protoc -I . --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` libcore.proto\n"
  },
  {
    "path": "go/grpc_server/go.mod",
    "content": "module grpc_server\n\ngo 1.19\n\nrequire (\n\tgithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0\n\tgithub.com/matsuridayo/libneko v1.0.0 // replaced\n\tgoogle.golang.org/grpc v1.49.0\n\tgoogle.golang.org/protobuf v1.28.1\n)\n\nrequire (\n\tgithub.com/golang/protobuf v1.5.2 // indirect\n\tgithub.com/google/go-cmp v0.5.8 // indirect\n\tgolang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect\n\tgolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect\n\tgolang.org/x/text v0.3.7 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect\n)\n\nreplace github.com/matsuridayo/libneko v1.0.0 => ../../../libneko\n"
  },
  {
    "path": "go/grpc_server/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngo.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=\ngo.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\ngo.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=\ngolang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb h1:ZrsicilzPCS/Xr8qtBZZLpy4P9TYXAfl49ctG1/5tgw=\ngoogle.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=\ngoogle.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=\ngoogle.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "go/grpc_server/grpc.go",
    "content": "package grpc_server\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"grpc_server/auth\"\n\t\"grpc_server/gen\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/matsuridayo/libneko/neko_common\"\n\n\tgrpc_auth \"github.com/grpc-ecosystem/go-grpc-middleware/auth\"\n\t\"google.golang.org/grpc\"\n)\n\ntype BaseServer struct {\n\tgen.LibcoreServiceServer\n}\n\nfunc (s *BaseServer) Exit(ctx context.Context, in *gen.EmptyReq) (out *gen.EmptyResp, _ error) {\n\tout = &gen.EmptyResp{}\n\n\t// Connection closed\n\tos.Exit(0)\n\treturn\n}\n\nfunc RunCore(setupCore func(), server gen.LibcoreServiceServer) {\n\t_token := flag.String(\"token\", \"\", \"\")\n\t_port := flag.Int(\"port\", 19810, \"\")\n\t_debug := flag.Bool(\"debug\", false, \"\")\n\tflag.CommandLine.Parse(os.Args[2:])\n\n\tneko_common.Debug = *_debug\n\n\tgo func() {\n\t\tparent, err := os.FindProcess(os.Getppid())\n\t\tif err != nil {\n\t\t\tlog.Fatalln(\"find parent:\", err)\n\t\t}\n\t\tif runtime.GOOS == \"windows\" {\n\t\t\tstate, err := parent.Wait()\n\t\t\tlog.Fatalln(\"parent exited:\", state, err)\n\t\t} else {\n\t\t\tfor {\n\t\t\t\ttime.Sleep(time.Second * 10)\n\t\t\t\terr = parent.Signal(syscall.Signal(0))\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Fatalln(\"parent exited:\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\t// Libcore\n\tsetupCore()\n\n\t// GRPC\n\tlis, err := net.Listen(\"tcp\", \"127.0.0.1:\"+strconv.Itoa(*_port))\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to listen: %v\", err)\n\t}\n\n\ttoken := *_token\n\tif token == \"\" {\n\t\tos.Stderr.WriteString(\"Please set a token: \")\n\t\ts := bufio.NewScanner(os.Stdin)\n\t\tif s.Scan() {\n\t\t\ttoken = strings.TrimSpace(s.Text())\n\t\t}\n\t}\n\tif token == \"\" {\n\t\tfmt.Println(\"You must set a token\")\n\t\tos.Exit(0)\n\t}\n\tos.Stderr.WriteString(\"token is set\\n\")\n\n\tauther := auth.Authenticator{\n\t\tToken: token,\n\t}\n\n\ts := grpc.NewServer(\n\t\tgrpc.StreamInterceptor(grpc_auth.StreamServerInterceptor(auther.Authenticate)),\n\t\tgrpc.UnaryInterceptor(grpc_auth.UnaryServerInterceptor(auther.Authenticate)),\n\t)\n\tgen.RegisterLibcoreServiceServer(s, server)\n\n\tname := \"nekobox_core\"\n\n\tlog.Printf(\"%s grpc server listening at %v\\n\", name, lis.Addr())\n\tif err := s.Serve(lis); err != nil {\n\t\tlog.Fatalf(\"failed to serve: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "go/grpc_server/update.go",
    "content": "package grpc_server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"grpc_server/gen\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/matsuridayo/libneko/neko_common\"\n)\n\nvar update_download_url string\n\nfunc (s *BaseServer) Update(ctx context.Context, in *gen.UpdateReq) (*gen.UpdateResp, error) {\n\tret := &gen.UpdateResp{}\n\n\tclient := neko_common.CreateProxyHttpClient(neko_common.GetCurrentInstance())\n\n\tif in.Action == gen.UpdateAction_Check { // Check update\n\t\tctx, cancel := context.WithTimeout(ctx, time.Second*10)\n\t\tdefer cancel()\n\n\t\treq, _ := http.NewRequestWithContext(ctx, \"GET\", \"https://api.github.com/repos/MatsuriDayo/nekoray/releases\", nil)\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\tret.Error = err.Error()\n\t\t\treturn ret, nil\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tv := []struct {\n\t\t\tHtmlUrl string `json:\"html_url\"`\n\t\t\tAssets  []struct {\n\t\t\t\tName               string `json:\"name\"`\n\t\t\t\tBrowserDownloadUrl string `json:\"browser_download_url\"`\n\t\t\t} `json:\"assets\"`\n\t\t\tPrerelease bool   `json:\"prerelease\"`\n\t\t\tBody       string `json:\"body\"`\n\t\t}{}\n\t\terr = json.NewDecoder(resp.Body).Decode(&v)\n\t\tif err != nil {\n\t\t\tret.Error = err.Error()\n\t\t\treturn ret, nil\n\t\t}\n\n\t\tnowVer := strings.TrimLeft(neko_common.Version_neko, \"nekoray-\")\n\n\t\tvar search string\n\t\tif runtime.GOOS == \"windows\" && runtime.GOARCH == \"amd64\" {\n\t\t\tsearch = \"windows64\"\n\t\t} else if runtime.GOOS == \"linux\" && runtime.GOARCH == \"amd64\" {\n\t\t\tsearch = \"linux64\"\n\t\t} else if runtime.GOOS == \"darwin\" {\n\t\t\tsearch = \"macos-\" + runtime.GOARCH\n\t\t} else {\n\t\t\tret.Error = \"Not official support platform\"\n\t\t\treturn ret, nil\n\t\t}\n\n\t\tfor _, release := range v {\n\t\t\tif len(release.Assets) > 0 {\n\t\t\t\tfor _, asset := range release.Assets {\n\t\t\t\t\tif strings.Contains(asset.Name, nowVer) {\n\t\t\t\t\t\treturn ret, nil // No update\n\t\t\t\t\t}\n\t\t\t\t\tif strings.Contains(asset.Name, search) {\n\t\t\t\t\t\tif release.Prerelease && !in.CheckPreRelease {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tupdate_download_url = asset.BrowserDownloadUrl\n\t\t\t\t\t\tret.AssetsName = asset.Name\n\t\t\t\t\t\tret.DownloadUrl = asset.BrowserDownloadUrl\n\t\t\t\t\t\tret.ReleaseUrl = release.HtmlUrl\n\t\t\t\t\t\tret.ReleaseNote = release.Body\n\t\t\t\t\t\tret.IsPreRelease = release.Prerelease\n\t\t\t\t\t\treturn ret, nil // update\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else { // Download update\n\t\tif update_download_url == \"\" {\n\t\t\tret.Error = \"?\"\n\t\t\treturn ret, nil\n\t\t}\n\n\t\treq, _ := http.NewRequestWithContext(ctx, \"GET\", update_download_url, nil)\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\tret.Error = err.Error()\n\t\t\treturn ret, nil\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tf, err := os.OpenFile(\"../nekoray.zip\", os.O_TRUNC|os.O_CREATE|os.O_RDWR, 0644)\n\t\tif err != nil {\n\t\t\tret.Error = err.Error()\n\t\t\treturn ret, nil\n\t\t}\n\t\tdefer f.Close()\n\n\t\t_, err = io.Copy(f, resp.Body)\n\t\tif err != nil {\n\t\t\tret.Error = err.Error()\n\t\t\treturn ret, nil\n\t\t}\n\t\tf.Sync()\n\t}\n\n\treturn ret, nil\n}\n"
  },
  {
    "path": "libs/.gitignore",
    "content": "/deps*\ndownloaded"
  },
  {
    "path": "libs/build_deps_all.sh",
    "content": "#!/bin/bash\nset -e\n\ncd libs\n\n# 参数\nif [ -z $cmake ]; then\n  cmake=\"cmake\"\nfi\nif [ -z $deps ]; then\n  deps=\"deps\"\nfi\n\n# libs/deps/...\nmkdir -p $deps\ncd $deps\nif [ -z $NKR_PACKAGE ]; then\n  INSTALL_PREFIX=$PWD/built\nelse\n  INSTALL_PREFIX=$PWD/package\nfi\nrm -rf $INSTALL_PREFIX\nmkdir -p $INSTALL_PREFIX\n\n#### clean ####\nclean() {\n  rm -rf dl.zip yaml-* zxing-* protobuf\n}\n\n#### ZXing v2.0.0 ####\ncurl -L -o dl.zip https://github.com/nu-book/zxing-cpp/archive/refs/tags/v2.0.0.zip\nunzip dl.zip\n\ncd zxing-*\nmkdir -p build\ncd build\n\n$cmake .. -GNinja -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=OFF -DBUILD_BLACKBOX_TESTS=OFF -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX\nninja && ninja install\n\ncd ../..\n\n#### yaml-cpp ####\ncurl -L -o dl.zip https://github.com/jbeder/yaml-cpp/archive/refs/tags/yaml-cpp-0.7.0.zip\nunzip dl.zip\n\ncd yaml-*\nmkdir -p build\ncd build\n\n$cmake .. -GNinja -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX\nninja && ninja install\n\ncd ../..\n\n#### protobuf ####\ngit clone --recurse-submodules -b v21.4 --depth 1 --shallow-submodules https://github.com/protocolbuffers/protobuf\n\n#备注：交叉编译要在 host 也安装 protobuf 并且版本一致,编译安装，同参数，安装到 /usr/local\n\nmkdir -p protobuf/build\ncd protobuf/build\n\n$cmake .. -GNinja \\\n  -DCMAKE_BUILD_TYPE=Release \\\n  -DBUILD_SHARED_LIBS=OFF \\\n  -Dprotobuf_MSVC_STATIC_RUNTIME=OFF \\\n  -Dprotobuf_BUILD_TESTS=OFF \\\n  -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX\nninja && ninja install\n\ncd ../..\n\n####\nclean\n"
  },
  {
    "path": "libs/build_go.sh",
    "content": "#!/bin/bash\nset -e\n\nsource libs/env_deploy.sh\n[ \"$GOOS\" == \"windows\" ] && [ \"$GOARCH\" == \"amd64\" ] && DEST=$DEPLOYMENT/windows64 || true\n[ \"$GOOS\" == \"windows\" ] && [ \"$GOARCH\" == \"arm64\" ] && DEST=$DEPLOYMENT/windows-arm64 || true\n[ \"$GOOS\" == \"linux\" ] && [ \"$GOARCH\" == \"amd64\" ] && DEST=$DEPLOYMENT/linux64 || true\n[ \"$GOOS\" == \"linux\" ] && [ \"$GOARCH\" == \"arm64\" ] && DEST=$DEPLOYMENT/linux-arm64 || true\nif [ -z $DEST ]; then\n  echo \"Please set GOOS GOARCH\"\n  exit 1\nfi\nrm -rf $DEST\nmkdir -p $DEST\n\nexport CGO_ENABLED=0\n\n#### Go: updater ####\npushd go/cmd/updater\n[ \"$GOOS\" == \"darwin\" ] || go build -o $DEST -trimpath -ldflags \"-w -s\"\n[ \"$GOOS\" == \"linux\" ] && mv $DEST/updater $DEST/launcher || true\npopd\n\n#### Go: nekobox_core ####\npushd go/cmd/nekobox_core\ngo build -v -o $DEST -trimpath -ldflags \"-w -s -X github.com/matsuridayo/libneko/neko_common.Version_neko=$version_standalone\" -tags \"with_clash_api,with_gvisor,with_quic,with_wireguard,with_utls,with_ech\"\npopd\n"
  },
  {
    "path": "libs/build_public_res.sh",
    "content": "#!/bin/bash\nset -e\n\nsource libs/env_deploy.sh\nDEST=$DEPLOYMENT/public_res\nrm -rf $DEST\nmkdir -p $DEST\n\n#### Download geodata ####\ncurl -fLso $DEST/geoip.dat \"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat\"\ncurl -fLso $DEST/geosite.dat \"https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat\"\ncurl -fLso $DEST/geoip.db \"https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db\"\ncurl -fLso $DEST/geosite.db \"https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db\"\n\n#### copy res/public ####\ncp res/public/* $DEST\n"
  },
  {
    "path": "libs/deploy_linux64.sh",
    "content": "#!/bin/bash\nset -e\n\nsource libs/env_deploy.sh\nDEST=$DEPLOYMENT/linux64\nrm -rf $DEST\nmkdir -p $DEST\n\n#### copy binary ####\ncp $BUILD/nekobox $DEST\n\n#### Download: prebuilt runtime ####\ncurl -Lso usr.zip https://github.com/MatsuriDayo/nekoray_qt_runtime/releases/download/20220503/20230202-5.12.8-ubuntu20.04-linux64.zip\nunzip usr.zip\nmv usr $DEST\n\n\n#### copy so ####\n# 5.11 looks buggy on new systems...\nexit\n\nUSR_LIB=/usr/lib/x86_64-linux-gnu\nmkdir usr\npushd usr\nmkdir lib\npushd lib\ncp $USR_LIB/libQt5Core.so.5 .\ncp $USR_LIB/libQt5DBus.so.5 .\ncp $USR_LIB/libQt5Gui.so.5 .\ncp $USR_LIB/libQt5Network.so.5 .\ncp $USR_LIB/libQt5Svg.so.5 .\ncp $USR_LIB/libQt5Widgets.so.5 .\ncp $USR_LIB/libQt5X11Extras.so.5 .\ncp $USR_LIB/libQt5XcbQpa.so.5 .\ncp $USR_LIB/libdouble-conversion.so.? .\ncp $USR_LIB/libxcb-util.so.? .\ncp $USR_LIB/libicuuc.so.?? .\ncp $USR_LIB/libicui18n.so.?? .\ncp $USR_LIB/libicudata.so.?? .\npopd\nmkdir plugins\npushd plugins\ncp -r $USR_LIB/qt5/plugins/bearer .\ncp -r $USR_LIB/qt5/plugins/iconengines .\ncp -r $USR_LIB/qt5/plugins/imageformats .\ncp -r $USR_LIB/qt5/plugins/platforminputcontexts .\ncp -r $USR_LIB/qt5/plugins/platforms .\ncp -r $USR_LIB/qt5/plugins/xcbglintegrations .\npopd\npopd\nmv usr $DEST\n"
  },
  {
    "path": "libs/deploy_windows64.sh",
    "content": "#!/bin/bash\nset -e\n\nsource libs/env_deploy.sh\nDEST=$DEPLOYMENT/windows64\nrm -rf $DEST\nmkdir -p $DEST\n\n#### copy exe ####\ncp $BUILD/nekobox.exe $DEST\n\n#### deploy qt & DLL runtime ####\npushd $DEST\nwindeployqt nekobox.exe --no-compiler-runtime --no-system-d3d-compiler --no-opengl-sw --verbose 2\nrm -rf translations\nrm -rf libEGL.dll libGLESv2.dll Qt6Pdf.dll\n\nif [ \"$DL_QT_VER\" != \"5.15\" ]; then\n  cp $SRC_ROOT/qtsdk/Qt/bin/libcrypto-3-x64.dll .\n  cp $SRC_ROOT/qtsdk/Qt/bin/libssl-3-x64.dll .\nfi\n\npopd\n\n#### prepare deployment ####\ncp $BUILD/*.pdb $DEPLOYMENT\n"
  },
  {
    "path": "libs/download_qtsdk_win.sh",
    "content": "mkdir qtsdk\ncd qtsdk\nif [ \"$DL_QT_VER\" == \"5.15\" ]; then\n  curl -LSO https://github.com/MatsuriDayo/nekoray_qt_runtime/releases/download/20220503/Qt5.15.7-Windows-x86_64-VS2019-16.11.20-20221103.7z\nelse\n  curl -LSO https://github.com/MatsuriDayo/nekoray_qt_runtime/releases/download/20220503/Qt6.7.2-Windows-x86_64-VS2022-17.10.3-20240621.7z\nfi\n7z x *.7z\nrm *.7z\nmv Qt* Qt\n"
  },
  {
    "path": "libs/env_deploy.sh",
    "content": "SRC_ROOT=\"$PWD\"\nDEPLOYMENT=\"$SRC_ROOT/deployment\"\nBUILD=\"$SRC_ROOT/build\"\nversion_standalone=\"nekoray-\"$(cat nekoray_version.txt) # 下次改\n"
  },
  {
    "path": "libs/env_qtsdk.sh",
    "content": "echo \"Setting Qt Sdk Dir to\" \"$1\"\nexport Qt5_DIR=\"$1\"\nexport Qt6_DIR=$Qt5_DIR\nexport PATH=$PATH:$Qt5_DIR/bin\nexport LD_LIBRARY_PATH=$Qt5_DIR/lib\nexport PKG_CONFIG_PATH=$Qt5_DIR/lib/pkgconfig\nexport QT_PLUGIN_PATH=$Qt5_DIR/plugins\nexport QML2_IMPORT_PATH=$Qt5_DIR/lib/qml\n"
  },
  {
    "path": "libs/format_cpp.sh",
    "content": "#!/bin/sh\ngit ls-files | grep -E \"\\.cpp|\\.h\" | grep -v \"3rdparty\" | xargs -n1 clang-format -i\n"
  },
  {
    "path": "libs/get_source.sh",
    "content": "#!/bin/bash\nset -e\n\nsource libs/env_deploy.sh\nENV_NEKORAY=1\nsource libs/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\"\n\npopd\n\n####\n\nif [ ! -d \"sing-quic\" ]; then\n  git clone --no-checkout https://github.com/MatsuriDayo/sing-quic.git\nfi\npushd sing-quic\ngit checkout \"$COMMIT_SING_QUIC\"\n\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\"\n\npopd\n\n####\n\npopd\n"
  },
  {
    "path": "libs/get_source_env.sh",
    "content": "export COMMIT_SING_BOX=\"06557f6cef23160668122a17a818b378b5a216b5\"\nexport COMMIT_SING_QUIC=\"b49ce60d9b3622d5238fee96bfd3c5f6e3915b42\"\nexport COMMIT_LIBNEKO=\"1c47a3af71990a7b2192e03292b4d246c308ef0b\"\n"
  },
  {
    "path": "libs/package_appimage.sh",
    "content": "#!/bin/bash\n\nsudo apt-get install fuse -y\n\ncp -r linux64 nekobox.AppDir\n\n# The file for Appimage\n\nrm nekobox.AppDir/launcher\n\ncat >nekobox.AppDir/nekobox.desktop <<-EOF\n[Desktop Entry]\nName=nekobox\nExec=echo \"nekobox started\"\nIcon=nekobox\nType=Application\nCategories=Network\nEOF\n\ncat >nekobox.AppDir/AppRun <<-EOF\n#!/bin/bash\necho \"PATH: \\${PATH}\"\necho \"nekobox runing on: \\$APPDIR\"\nLD_LIBRARY_PATH=\\${APPDIR}/usr/lib QT_PLUGIN_PATH=\\${APPDIR}/usr/plugins \\${APPDIR}/nekobox -appdata \"\\$@\"\nEOF\n\nchmod +x nekobox.AppDir/AppRun\n\n# build\n\ncurl -fLSO https://github.com/AppImage/AppImageKit/releases/latest/download/appimagetool-x86_64.AppImage\nchmod +x appimagetool-x86_64.AppImage\n./appimagetool-x86_64.AppImage nekobox.AppDir\n\n# clean\n\nrm appimagetool-x86_64.AppImage\nrm -rf nekobox.AppDir\n"
  },
  {
    "path": "libs/package_debian.sh",
    "content": "#!/bin/bash\n\nversion=\"$1\"\n\nmkdir -p nekoray/DEBIAN\nmkdir -p nekoray/opt\ncp -r linux64 nekoray/opt/\nmv nekoray/opt/linux64 nekoray/opt/nekoray\nrm -rf nekoray/opt/nekoray/usr\nrm nekoray/opt/nekoray/launcher\n\n# basic\ncat >nekoray/DEBIAN/control <<-EOF\nPackage: nekoray\nVersion: $version\nArchitecture: amd64\nMaintainer: MatsuriDayo nekoha_matsuri@protonmail.com\nDepends: libxcb-xinerama0, libqt5core5a, libqt5gui5, libqt5network5, libqt5widgets5, libqt5svg5, libqt5x11extras5, desktop-file-utils\nDescription: Qt based cross-platform GUI proxy configuration manager (backend: v2ray / sing-box)\nEOF\n\ncat >nekoray/DEBIAN/postinst <<-EOF\nif [ ! -s /usr/share/applications/nekoray.desktop ]; then\n    cat >/usr/share/applications/nekoray.desktop<<-END\n[Desktop Entry]\nName=nekoray\nComment=Qt based cross-platform GUI proxy configuration manager (backend: sing-box)\nExec=sh -c \"PATH=/opt/nekoray:\\$PATH /opt/nekoray/nekobox -appdata\"\nIcon=/opt/nekoray/nekobox.png\nTerminal=false\nType=Application\nCategories=Network;Application;\nEND\nfi\n\nsetcap cap_net_admin=ep /opt/nekoray/nekobox_core\n\nupdate-desktop-database\nEOF\n\nsudo chmod 0755 nekoray/DEBIAN/postinst\n\n# desktop && PATH\n\nsudo dpkg-deb -Zxz --build nekoray\n"
  },
  {
    "path": "main/Const.hpp",
    "content": "#pragma once\n\nnamespace NekoGui {\n    namespace DomainMatcher {\n        enum DomainMatcher {\n            DEFAULT,\n            MPH,\n        };\n    }\n\n    namespace SniffingMode {\n        enum SniffingMode {\n            DISABLE,\n            FOR_ROUTING,\n            FOR_DESTINATION,\n        };\n    }\n\n    namespace CoreType {\n        enum CoreType {\n            V2RAY, // DO NOT USE\n            SING_BOX,\n        };\n    }\n} // namespace NekoGui\n"
  },
  {
    "path": "main/GuiUtils.hpp",
    "content": "#pragma once\n\n// Dialogs\n\n#define Dialog_DialogBasicSettings \"DialogBasicSettings\"\n#define Dialog_DialogEditProfile \"DialogEditProfile\"\n#define Dialog_DialogManageGroups \"DialogManageGroups\"\n#define Dialog_DialogManageRoutes \"DialogManageRoutes\"\n\n// Utils\n\n#define QRegExpValidator_Number new QRegularExpressionValidator(QRegularExpression(\"^[0-9]+$\"), this)\n\n// NekoRay Save&Load\n\n#define P_C_LOAD_STRING(a) CACHE.a = bean->a;\n#define P_C_SAVE_STRING(a) bean->a = CACHE.a;\n#define D_C_LOAD_STRING(a) CACHE.a = NekoGui::dataStore->a;\n#define D_C_SAVE_STRING(a) NekoGui::dataStore->a = CACHE.a;\n\n#define P_LOAD_STRING(a) ui->a->setText(bean->a);\n#define P_LOAD_STRING_PLAIN(a) ui->a->setPlainText(bean->a);\n#define P_SAVE_STRING(a) bean->a = ui->a->text();\n#define P_SAVE_STRING_PLAIN(a) bean->a = ui->a->toPlainText();\n\n#define D_LOAD_STRING(a) ui->a->setText(NekoGui::dataStore->a);\n#define D_LOAD_STRING_PLAIN(a) ui->a->setPlainText(NekoGui::dataStore->a);\n#define D_SAVE_STRING(a) NekoGui::dataStore->a = ui->a->text();\n#define D_SAVE_STRING_PLAIN(a) NekoGui::dataStore->a = ui->a->toPlainText();\n\n#define P_LOAD_INT(a)                    \\\n    ui->a->setText(Int2String(bean->a)); \\\n    ui->a->setValidator(QRegExpValidator_Number);\n#define P_SAVE_INT(a) bean->a = ui->a->text().toInt();\n\n#define D_LOAD_INT(a)                                  \\\n    ui->a->setText(Int2String(NekoGui::dataStore->a)); \\\n    ui->a->setValidator(QRegExpValidator_Number);\n#define D_SAVE_INT(a) NekoGui::dataStore->a = ui->a->text().toInt();\n\n#define P_LOAD_COMBO_STRING(a) ui->a->setCurrentText(bean->a);\n#define P_SAVE_COMBO_STRING(a) bean->a = ui->a->currentText();\n\n#define D_LOAD_COMBO_STRING(a) ui->a->setCurrentText(NekoGui::dataStore->a);\n#define D_SAVE_COMBO_STRING(a) NekoGui::dataStore->a = ui->a->currentText();\n\n#define P_LOAD_COMBO_INT(a) ui->a->setCurrentIndex(bean->a);\n#define P_SAVE_COMBO_INT(a) bean->a = ui->a->currentIndex();\n\n#define D_LOAD_BOOL(a) ui->a->setChecked(NekoGui::dataStore->a);\n#define D_SAVE_BOOL(a) NekoGui::dataStore->a = ui->a->isChecked();\n\n#define P_LOAD_BOOL(a) ui->a->setChecked(bean->a);\n#define P_SAVE_BOOL(a) bean->a = ui->a->isChecked();\n\n#define D_LOAD_INT_ENABLE(i, e)                             \\\n    if (NekoGui::dataStore->i > 0) {                        \\\n        ui->e->setChecked(true);                            \\\n        ui->i->setText(Int2String(NekoGui::dataStore->i));  \\\n    } else {                                                \\\n        ui->e->setChecked(false);                           \\\n        ui->i->setText(Int2String(-NekoGui::dataStore->i)); \\\n    }                                                       \\\n    ui->i->setValidator(QRegExpValidator_Number);\n#define D_SAVE_INT_ENABLE(i, e)                         \\\n    if (ui->e->isChecked()) {                           \\\n        NekoGui::dataStore->i = ui->i->text().toInt();  \\\n    } else {                                            \\\n        NekoGui::dataStore->i = -ui->i->text().toInt(); \\\n    }\n\n#define C_EDIT_JSON_ALLOW_EMPTY(a)                                    \\\n    auto editor = new JsonEditor(QString2QJsonObject(CACHE.a), this); \\\n    auto result = editor->OpenEditor();                               \\\n    CACHE.a = QJsonObject2QString(result, true);                      \\\n    if (result.isEmpty()) CACHE.a = \"\";                               \\\n    editor->deleteLater();\n\n//\n\n#define ADD_ASTERISK(parent)                                         \\\n    for (auto label: parent->findChildren<QLabel *>()) {             \\\n        auto text = label->text();                                   \\\n        if (!label->toolTip().isEmpty() && !text.endsWith(\"*\")) {    \\\n            label->setText(text + \"*\");                              \\\n        }                                                            \\\n    }                                                                \\\n    for (auto checkBox: parent->findChildren<QCheckBox *>()) {       \\\n        auto text = checkBox->text();                                \\\n        if (!checkBox->toolTip().isEmpty() && !text.endsWith(\"*\")) { \\\n            checkBox->setText(text + \"*\");                           \\\n        }                                                            \\\n    }\n"
  },
  {
    "path": "main/HTTPRequestHelper.cpp",
    "content": "#include \"HTTPRequestHelper.hpp\"\n\n#include <QByteArray>\n#include <QNetworkProxy>\n#include <QEventLoop>\n#include <QMetaEnum>\n#include <QTimer>\n\n#include \"main/NekoGui.hpp\"\n\nnamespace NekoGui_network {\n\n    NekoHTTPResponse NetworkRequestHelper::HttpGet(const QUrl &url) {\n        QNetworkRequest request;\n        QNetworkAccessManager accessManager;\n        request.setUrl(url);\n        // Set proxy\n        if (NekoGui::dataStore->sub_use_proxy) {\n            QNetworkProxy p;\n            // Note: sing-box mixed socks5 protocol error\n            p.setType(QNetworkProxy::HttpProxy);\n            p.setHostName(\"127.0.0.1\");\n            p.setPort(NekoGui::dataStore->inbound_socks_port);\n            if (NekoGui::dataStore->inbound_auth->NeedAuth()) {\n                p.setUser(NekoGui::dataStore->inbound_auth->username);\n                p.setPassword(NekoGui::dataStore->inbound_auth->password);\n            }\n            accessManager.setProxy(p);\n            if (NekoGui::dataStore->started_id < 0) {\n                return NekoHTTPResponse{QObject::tr(\"Request with proxy but no profile started.\")};\n            }\n        }\n        if (accessManager.proxy().type() == QNetworkProxy::Socks5Proxy) {\n            auto cap = accessManager.proxy().capabilities();\n            accessManager.proxy().setCapabilities(cap | QNetworkProxy::HostNameLookupCapability);\n        }\n        // Set attribute\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))\n        request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);\n#endif\n        request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, NekoGui::dataStore->GetUserAgent());\n        if (NekoGui::dataStore->sub_insecure) {\n            QSslConfiguration c;\n            c.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone);\n            request.setSslConfiguration(c);\n        }\n        //\n        auto _reply = accessManager.get(request);\n        connect(_reply, &QNetworkReply::sslErrors, _reply, [](const QList<QSslError> &errors) {\n            QStringList error_str;\n            for (const auto &err: errors) {\n                error_str << err.errorString();\n            }\n            MW_show_log(QStringLiteral(\"SSL Errors: %1 %2\").arg(error_str.join(\",\"), NekoGui::dataStore->sub_insecure ? \"(Ignored)\" : \"\"));\n        });\n        // Wait for response\n        auto abortTimer = new QTimer;\n        abortTimer->setSingleShot(true);\n        abortTimer->setInterval(10000);\n        QObject::connect(abortTimer, &QTimer::timeout, _reply, &QNetworkReply::abort);\n        abortTimer->start();\n        {\n            QEventLoop loop;\n            QObject::connect(_reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);\n            loop.exec();\n        }\n        if (abortTimer != nullptr) {\n            abortTimer->stop();\n            abortTimer->deleteLater();\n        }\n        //\n        auto result = NekoHTTPResponse{_reply->error() == QNetworkReply::NetworkError::NoError ? \"\" : _reply->errorString(),\n                                       _reply->readAll(), _reply->rawHeaderPairs()};\n        _reply->deleteLater();\n        return result;\n    }\n\n    QString NetworkRequestHelper::GetHeader(const QList<QPair<QByteArray, QByteArray>> &header, const QString &name) {\n        for (const auto &p: header) {\n            if (QString(p.first).toLower() == name.toLower()) return p.second;\n        }\n        return \"\";\n    }\n\n} // namespace NekoGui_network\n"
  },
  {
    "path": "main/HTTPRequestHelper.hpp",
    "content": "#pragma once\n\n#include <QNetworkAccessManager>\n#include <QNetworkReply>\n#include <QNetworkRequest>\n#include <QObject>\n#include <functional>\n\nnamespace NekoGui_network {\n    struct NekoHTTPResponse {\n        QString error;\n        QByteArray data;\n        QList<QPair<QByteArray, QByteArray>> header;\n    };\n\n    class NetworkRequestHelper : QObject {\n        Q_OBJECT\n\n        explicit NetworkRequestHelper(QObject *parent) : QObject(parent){};\n\n        ~NetworkRequestHelper() override = default;\n        ;\n\n    public:\n        static NekoHTTPResponse HttpGet(const QUrl &url);\n\n        static QString GetHeader(const QList<QPair<QByteArray, QByteArray>> &header, const QString &name);\n    };\n} // namespace NekoGui_network\n\nusing namespace NekoGui_network;\n"
  },
  {
    "path": "main/NekoGui.cpp",
    "content": "#include \"NekoGui.hpp\"\n#include \"fmt/Preset.hpp\"\n\n#include <QFile>\n#include <QDir>\n#include <QApplication>\n#include <QFileInfo>\n#include <QJsonObject>\n#include <QJsonArray>\n#include <QJsonDocument>\n\n#ifdef Q_OS_WIN\n#include \"sys/windows/guihelper.h\"\n#else\n#ifdef Q_OS_LINUX\n#include <sys/linux/LinuxCap.h>\n#endif\n#include <unistd.h>\n#endif\n\nnamespace NekoGui_ConfigItem {\n\n    // 添加关联\n    void JsonStore::_add(configItem *item) {\n        _map.insert(item->name, std::shared_ptr<configItem>(item));\n    }\n\n    QString JsonStore::_name(void *p) {\n        for (const auto &_item: _map) {\n            if (_item->ptr == p) return _item->name;\n        }\n        return {};\n    }\n\n    std::shared_ptr<configItem> JsonStore::_get(const QString &name) {\n        // 直接 [] 会设置一个 nullptr ，所以先判断是否存在\n        if (_map.contains(name)) {\n            return _map[name];\n        }\n        return nullptr;\n    }\n\n    void JsonStore::_setValue(const QString &name, void *p) {\n        auto item = _get(name);\n        if (item == nullptr) return;\n\n        switch (item->type) {\n            case itemType::string:\n                *(QString *) item->ptr = *(QString *) p;\n                break;\n            case itemType::boolean:\n                *(bool *) item->ptr = *(bool *) p;\n                break;\n            case itemType::integer:\n                *(int *) item->ptr = *(int *) p;\n                break;\n            case itemType::integer64:\n                *(long long *) item->ptr = *(long long *) p;\n                break;\n            // others...\n            case stringList:\n            case integerList:\n            case jsonStore:\n                break;\n        }\n    }\n\n    QJsonObject JsonStore::ToJson(const QStringList &without) {\n        QJsonObject object;\n        for (const auto &_item: _map) {\n            auto item = _item.get();\n            if (without.contains(item->name)) continue;\n            switch (item->type) {\n                case itemType::string:\n                    // Allow Empty\n                    if (!((QString *) item->ptr)->isEmpty()) {\n                        object.insert(item->name, *(QString *) item->ptr);\n                    }\n                    break;\n                case itemType::integer:\n                    object.insert(item->name, *(int *) item->ptr);\n                    break;\n                case itemType::integer64:\n                    object.insert(item->name, *(long long *) item->ptr);\n                    break;\n                case itemType::boolean:\n                    object.insert(item->name, *(bool *) item->ptr);\n                    break;\n                case itemType::stringList:\n                    object.insert(item->name, QList2QJsonArray<QString>(*(QList<QString> *) item->ptr));\n                    break;\n                case itemType::integerList:\n                    object.insert(item->name, QList2QJsonArray<int>(*(QList<int> *) item->ptr));\n                    break;\n                case itemType::jsonStore:\n                    // _add 时应关联对应 JsonStore 的指针\n                    object.insert(item->name, ((JsonStore *) item->ptr)->ToJson());\n                    break;\n            }\n        }\n        return object;\n    }\n\n    QByteArray JsonStore::ToJsonBytes() {\n        QJsonDocument document;\n        document.setObject(ToJson());\n        return document.toJson(save_control_compact ? QJsonDocument::Compact : QJsonDocument::Indented);\n    }\n\n    void JsonStore::FromJson(QJsonObject object) {\n        for (const auto &key: object.keys()) {\n            if (_map.count(key) == 0) {\n                continue;\n            }\n\n            auto value = object[key];\n            auto item = _map[key].get();\n\n            if (item == nullptr)\n                continue; // 故意忽略\n\n            // 根据类型修改ptr的内容\n            switch (item->type) {\n                case itemType::string:\n                    if (value.type() != QJsonValue::String) {\n                        continue;\n                    }\n                    *(QString *) item->ptr = value.toString();\n                    break;\n                case itemType::integer:\n                    if (value.type() != QJsonValue::Double) {\n                        continue;\n                    }\n                    *(int *) item->ptr = value.toInt();\n                    break;\n                case itemType::integer64:\n                    if (value.type() != QJsonValue::Double) {\n                        continue;\n                    }\n                    *(long long *) item->ptr = value.toDouble();\n                    break;\n                case itemType::boolean:\n                    if (value.type() != QJsonValue::Bool) {\n                        continue;\n                    }\n                    *(bool *) item->ptr = value.toBool();\n                    break;\n                case itemType::stringList:\n                    if (value.type() != QJsonValue::Array) {\n                        continue;\n                    }\n                    *(QList<QString> *) item->ptr = QJsonArray2QListString(value.toArray());\n                    break;\n                case itemType::integerList:\n                    if (value.type() != QJsonValue::Array) {\n                        continue;\n                    }\n                    *(QList<int> *) item->ptr = QJsonArray2QListInt(value.toArray());\n                    break;\n                case itemType::jsonStore:\n                    if (value.type() != QJsonValue::Object) {\n                        continue;\n                    }\n                    ((JsonStore *) item->ptr)->FromJson(value.toObject());\n                    break;\n            }\n        }\n\n        if (callback_after_load != nullptr) callback_after_load();\n    }\n\n    void JsonStore::FromJsonBytes(const QByteArray &data) {\n        QJsonParseError error{};\n        auto document = QJsonDocument::fromJson(data, &error);\n\n        if (error.error != error.NoError) {\n            qDebug() << \"QJsonParseError\" << error.errorString();\n            return;\n        }\n\n        FromJson(document.object());\n    }\n\n    bool JsonStore::Save() {\n        if (callback_before_save != nullptr) callback_before_save();\n        if (save_control_no_save) return false;\n\n        auto save_content = ToJsonBytes();\n        auto changed = last_save_content != save_content;\n        last_save_content = save_content;\n\n        QFile file;\n        file.setFileName(fn);\n        file.open(QIODevice::ReadWrite | QIODevice::Truncate);\n        file.write(save_content);\n        file.close();\n\n        return changed;\n    }\n\n    bool JsonStore::Load() {\n        QFile file;\n        file.setFileName(fn);\n\n        if (!file.exists() && !load_control_must) {\n            return false;\n        }\n\n        bool ok = file.open(QIODevice::ReadOnly);\n        if (!ok) {\n            MessageBoxWarning(\"error\", \"can not open config \" + fn + \"\\n\" + file.errorString());\n        } else {\n            last_save_content = file.readAll();\n            FromJsonBytes(last_save_content);\n        }\n\n        file.close();\n        return ok;\n    }\n\n} // namespace NekoGui_ConfigItem\n\nnamespace NekoGui {\n\n    DataStore *dataStore = new DataStore();\n\n    // datastore\n\n    DataStore::DataStore() : JsonStore() {\n        _add(new configItem(\"extraCore\", dynamic_cast<JsonStore *>(extraCore), itemType::jsonStore));\n        _add(new configItem(\"inbound_auth\", dynamic_cast<JsonStore *>(inbound_auth), itemType::jsonStore));\n\n        _add(new configItem(\"user_agent2\", &user_agent, itemType::string));\n        _add(new configItem(\"test_url\", &test_latency_url, itemType::string));\n        _add(new configItem(\"test_url_dl\", &test_download_url, itemType::string));\n        _add(new configItem(\"test_dl_timeout\", &test_download_timeout, itemType::integer));\n        _add(new configItem(\"current_group\", &current_group, itemType::integer));\n        _add(new configItem(\"inbound_address\", &inbound_address, itemType::string));\n        _add(new configItem(\"inbound_socks_port\", &inbound_socks_port, itemType::integer));\n        _add(new configItem(\"log_level\", &log_level, itemType::string));\n        _add(new configItem(\"mux_protocol\", &mux_protocol, itemType::string));\n        _add(new configItem(\"mux_concurrency\", &mux_concurrency, itemType::integer));\n        _add(new configItem(\"mux_padding\", &mux_padding, itemType::boolean));\n        _add(new configItem(\"mux_default_on\", &mux_default_on, itemType::boolean));\n        _add(new configItem(\"traffic_loop_interval\", &traffic_loop_interval, itemType::integer));\n        _add(new configItem(\"test_concurrent\", &test_concurrent, itemType::integer));\n        _add(new configItem(\"theme\", &theme, itemType::string));\n        _add(new configItem(\"custom_inbound\", &custom_inbound, itemType::string));\n        _add(new configItem(\"custom_route\", &custom_route_global, itemType::string));\n        _add(new configItem(\"sub_use_proxy\", &sub_use_proxy, itemType::boolean));\n        _add(new configItem(\"remember_id\", &remember_id, itemType::integer));\n        _add(new configItem(\"remember_enable\", &remember_enable, itemType::boolean));\n        _add(new configItem(\"language\", &language, itemType::integer));\n        _add(new configItem(\"spmode2\", &remember_spmode, itemType::stringList));\n        _add(new configItem(\"skip_cert\", &skip_cert, itemType::boolean));\n        _add(new configItem(\"hk_mw\", &hotkey_mainwindow, itemType::string));\n        _add(new configItem(\"hk_group\", &hotkey_group, itemType::string));\n        _add(new configItem(\"hk_route\", &hotkey_route, itemType::string));\n        _add(new configItem(\"hk_spmenu\", &hotkey_system_proxy_menu, itemType::string));\n        _add(new configItem(\"fakedns\", &fake_dns, itemType::boolean));\n        _add(new configItem(\"active_routing\", &active_routing, itemType::string));\n        _add(new configItem(\"mw_size\", &mw_size, itemType::string));\n        _add(new configItem(\"conn_stat\", &connection_statistics, itemType::boolean));\n        _add(new configItem(\"vpn_impl\", &vpn_implementation, itemType::integer));\n        _add(new configItem(\"vpn_mtu\", &vpn_mtu, itemType::integer));\n        _add(new configItem(\"vpn_ipv6\", &vpn_ipv6, itemType::boolean));\n        _add(new configItem(\"vpn_hide_console\", &vpn_hide_console, itemType::boolean));\n        _add(new configItem(\"vpn_strict_route\", &vpn_strict_route, itemType::boolean));\n        _add(new configItem(\"vpn_bypass_process\", &vpn_rule_process, itemType::string));\n        _add(new configItem(\"vpn_bypass_cidr\", &vpn_rule_cidr, itemType::string));\n        _add(new configItem(\"vpn_rule_white\", &vpn_rule_white, itemType::boolean));\n        _add(new configItem(\"check_include_pre\", &check_include_pre, itemType::boolean));\n        _add(new configItem(\"sp_format\", &system_proxy_format, itemType::string));\n        _add(new configItem(\"sub_clear\", &sub_clear, itemType::boolean));\n        _add(new configItem(\"sub_insecure\", &sub_insecure, itemType::boolean));\n        _add(new configItem(\"sub_auto_update\", &sub_auto_update, itemType::integer));\n        _add(new configItem(\"log_ignore\", &log_ignore, itemType::stringList));\n        _add(new configItem(\"start_minimal\", &start_minimal, itemType::boolean));\n        _add(new configItem(\"max_log_line\", &max_log_line, itemType::integer));\n        _add(new configItem(\"splitter_state\", &splitter_state, itemType::string));\n        _add(new configItem(\"utlsFingerprint\", &utlsFingerprint, itemType::string));\n        _add(new configItem(\"core_box_clash_api\", &core_box_clash_api, itemType::integer));\n        _add(new configItem(\"core_box_clash_api_secret\", &core_box_clash_api_secret, itemType::string));\n        _add(new configItem(\"core_box_underlying_dns\", &core_box_underlying_dns, itemType::string));\n        _add(new configItem(\"vpn_internal_tun\", &vpn_internal_tun, itemType::boolean));\n    }\n\n    void DataStore::UpdateStartedId(int id) {\n        started_id = id;\n        if (remember_enable) {\n            remember_id = id;\n            Save();\n        } else if (remember_id >= 0) {\n            remember_id = -1919;\n            Save();\n        }\n    }\n\n    QString DataStore::GetUserAgent(bool isDefault) const {\n        if (user_agent.isEmpty()) {\n            isDefault = true;\n        }\n        if (isDefault) {\n            QString version = SubStrBefore(NKR_VERSION, \"-\");\n            if (!version.contains(\".\")) version = \"2.0\";\n            return \"NekoBox/PC/\" + version + \" (Prefer ClashMeta Format)\";\n        }\n        return user_agent;\n    }\n\n    // preset routing\n    Routing::Routing(int preset) : JsonStore() {\n        if (preset == 1) {\n            direct_ip =\n                \"geoip:cn\\n\"\n                \"geoip:private\";\n            direct_domain = \"geosite:cn\";\n            proxy_ip = \"\";\n            proxy_domain = \"\";\n            block_ip = \"\";\n            block_domain =\n                \"geosite:category-ads-all\\n\"\n                \"domain:appcenter.ms\\n\"\n                \"domain:firebase.io\\n\"\n                \"domain:crashlytics.com\\n\";\n        }\n        if (!Preset::SingBox::DomainStrategy.contains(domain_strategy)) domain_strategy = \"\";\n        if (!Preset::SingBox::DomainStrategy.contains(outbound_domain_strategy)) outbound_domain_strategy = \"\";\n        _add(new configItem(\"direct_ip\", &this->direct_ip, itemType::string));\n        _add(new configItem(\"direct_domain\", &this->direct_domain, itemType::string));\n        _add(new configItem(\"proxy_ip\", &this->proxy_ip, itemType::string));\n        _add(new configItem(\"proxy_domain\", &this->proxy_domain, itemType::string));\n        _add(new configItem(\"block_ip\", &this->block_ip, itemType::string));\n        _add(new configItem(\"block_domain\", &this->block_domain, itemType::string));\n        _add(new configItem(\"def_outbound\", &this->def_outbound, itemType::string));\n        _add(new configItem(\"custom\", &this->custom, itemType::string));\n        //\n        _add(new configItem(\"remote_dns\", &this->remote_dns, itemType::string));\n        _add(new configItem(\"remote_dns_strategy\", &this->remote_dns_strategy, itemType::string));\n        _add(new configItem(\"direct_dns\", &this->direct_dns, itemType::string));\n        _add(new configItem(\"direct_dns_strategy\", &this->direct_dns_strategy, itemType::string));\n        _add(new configItem(\"domain_strategy\", &this->domain_strategy, itemType::string));\n        _add(new configItem(\"outbound_domain_strategy\", &this->outbound_domain_strategy, itemType::string));\n        _add(new configItem(\"dns_routing\", &this->dns_routing, itemType::boolean));\n        _add(new configItem(\"sniffing_mode\", &this->sniffing_mode, itemType::integer));\n        _add(new configItem(\"use_dns_object\", &this->use_dns_object, itemType::boolean));\n        _add(new configItem(\"dns_object\", &this->dns_object, itemType::string));\n        _add(new configItem(\"dns_final_out\", &this->dns_final_out, itemType::string));\n    }\n\n    QString Routing::DisplayRouting() const {\n        return QStringLiteral(\"[Proxy] %1\\n[Proxy] %2\\n[Direct] %3\\n[Direct] %4\\n[Block] %5\\n[Block] %6\\n[Default Outbound] %7\\n[DNS] %8\")\n            .arg(SplitLinesSkipSharp(proxy_domain).join(\",\"), 10)\n            .arg(SplitLinesSkipSharp(proxy_ip).join(\",\"), 10)\n            .arg(SplitLinesSkipSharp(direct_domain).join(\",\"), 10)\n            .arg(SplitLinesSkipSharp(direct_ip).join(\",\"), 10)\n            .arg(SplitLinesSkipSharp(block_domain).join(\",\"), 10)\n            .arg(SplitLinesSkipSharp(block_ip).join(\",\"), 10)\n            .arg(def_outbound)\n            .arg(use_dns_object ? \"DNS Object\" : \"Simple DNS\");\n    }\n\n    QStringList Routing::List() {\n        QDir dr(ROUTES_PREFIX);\n        return dr.entryList(QDir::Files);\n    }\n\n    bool Routing::SetToActive(const QString &name) {\n        NekoGui::dataStore->routing = std::make_unique<Routing>();\n        NekoGui::dataStore->routing->load_control_must = true;\n        NekoGui::dataStore->routing->fn = ROUTES_PREFIX + name;\n        auto ok = NekoGui::dataStore->routing->Load();\n        if (ok) {\n            NekoGui::dataStore->active_routing = name;\n            NekoGui::dataStore->Save();\n        }\n        return ok;\n    }\n\n    // NO default extra core\n\n    ExtraCore::ExtraCore() : JsonStore() {\n        _add(new configItem(\"core_map\", &this->core_map, itemType::string));\n    }\n\n    QString ExtraCore::Get(const QString &id) const {\n        auto obj = QString2QJsonObject(core_map);\n        for (const auto &c: obj.keys()) {\n            if (c == id) return obj[id].toString();\n        }\n        return \"\";\n    }\n\n    void ExtraCore::Set(const QString &id, const QString &path) {\n        auto obj = QString2QJsonObject(core_map);\n        obj[id] = path;\n        core_map = QJsonObject2QString(obj, true);\n    }\n\n    void ExtraCore::Delete(const QString &id) {\n        auto obj = QString2QJsonObject(core_map);\n        obj.remove(id);\n        core_map = QJsonObject2QString(obj, true);\n    }\n\n    InboundAuthorization::InboundAuthorization() : JsonStore() {\n        _add(new configItem(\"user\", &this->username, itemType::string));\n        _add(new configItem(\"pass\", &this->password, itemType::string));\n    }\n\n    bool InboundAuthorization::NeedAuth() const {\n        return !username.trimmed().isEmpty() && !password.trimmed().isEmpty();\n    }\n\n    // System Utils\n\n    QString FindCoreAsset(const QString &name) {\n        QStringList search{};\n        search << QApplication::applicationDirPath();\n        search << \"/usr/share/sing-geoip\";\n        search << \"/usr/share/sing-geosite\";\n        search << \"/usr/share/sing-box\";\n        search << \"/usr/lib/nekobox\";\n        search << \"/usr/share/nekobox\";\n        for (const auto &dir: search) {\n            if (dir.isEmpty()) continue;\n            QFileInfo asset(dir + \"/\" + name);\n            if (asset.exists()) {\n                return asset.absoluteFilePath();\n            }\n        }\n        return {};\n    }\n\n    QString FindNekoBoxCoreRealPath() {\n        auto fn = QApplication::applicationDirPath() + \"/nekobox_core\";\n        auto fi = QFileInfo(fn);\n        if (fi.isSymLink()) return fi.symLinkTarget();\n        return fn;\n    }\n\n    short isAdminCache = -1;\n\n    // IsAdmin 主要判断：有无权限启动 Tun\n    bool IsAdmin() {\n        if (isAdminCache >= 0) return isAdminCache;\n\n        bool admin = false;\n#ifdef Q_OS_WIN\n        admin = Windows_IsInAdmin();\n#else\n#ifdef Q_OS_LINUX\n        admin |= Linux_GetCapString(FindNekoBoxCoreRealPath()).contains(\"cap_net_admin\");\n#endif\n        admin |= geteuid() == 0;\n#endif\n\n        isAdminCache = admin;\n        return admin;\n    };\n\n} // namespace NekoGui\n"
  },
  {
    "path": "main/NekoGui.hpp",
    "content": "#pragma once\n\n#include \"Const.hpp\"\n#include \"NekoGui_Utils.hpp\"\n#include \"NekoGui_ConfigItem.hpp\"\n#include \"NekoGui_DataStore.hpp\"\n\n// Switch core support\n\nnamespace NekoGui {\n    inline int coreType = CoreType::SING_BOX;\n\n    QString FindCoreAsset(const QString &name);\n\n    QString FindNekoBoxCoreRealPath();\n\n    bool IsAdmin();\n} // namespace NekoGui\n\n#define ROUTES_PREFIX_NAME QStringLiteral(\"routes_box\")\n#define ROUTES_PREFIX QString(ROUTES_PREFIX_NAME + \"/\")\n"
  },
  {
    "path": "main/NekoGui_ConfigItem.hpp",
    "content": "// DO NOT INCLUDE THIS\n\nnamespace NekoGui_ConfigItem {\n    // config 工具\n    enum itemType {\n        string,\n        integer,\n        integer64,\n        boolean,\n        stringList,\n        integerList,\n        jsonStore,\n    };\n\n    class configItem {\n    public:\n        QString name;\n        void *ptr;\n        itemType type;\n\n        configItem(QString n, void *p, itemType t) {\n            name = std::move(n);\n            ptr = p;\n            type = t;\n        }\n    };\n\n    // 可格式化对象\n    class JsonStore {\n    public:\n        QMap<QString, std::shared_ptr<configItem>> _map;\n\n        std::function<void()> callback_after_load = nullptr;\n        std::function<void()> callback_before_save = nullptr;\n\n        QString fn;\n        bool load_control_must = false; // must load from file\n        bool save_control_compact = false;\n        bool save_control_no_save = false;\n        QByteArray last_save_content;\n\n        JsonStore() = default;\n\n        explicit JsonStore(QString fileName) {\n            fn = std::move(fileName);\n        }\n\n        void _add(configItem *item);\n\n        QString _name(void *p);\n\n        std::shared_ptr<configItem> _get(const QString &name);\n\n        void _setValue(const QString &name, void *p);\n\n        QJsonObject ToJson(const QStringList &without = {});\n\n        QByteArray ToJsonBytes();\n\n        void FromJson(QJsonObject object);\n\n        void FromJsonBytes(const QByteArray &data);\n\n        bool Save();\n\n        bool Load();\n    };\n} // namespace NekoGui_ConfigItem\n\nusing namespace NekoGui_ConfigItem;\n"
  },
  {
    "path": "main/NekoGui_DataStore.hpp",
    "content": "// DO NOT INCLUDE THIS\n\nnamespace NekoGui {\n\n    class Routing : public JsonStore {\n    public:\n        QString direct_ip;\n        QString direct_domain;\n        QString proxy_ip;\n        QString proxy_domain;\n        QString block_ip;\n        QString block_domain;\n        QString def_outbound = \"proxy\";\n        QString custom = \"{\\\"rules\\\": []}\";\n\n        // DNS\n        QString remote_dns = \"https://dns.google/dns-query\";\n        QString remote_dns_strategy = \"\";\n        QString direct_dns = \"https://doh.pub/dns-query\";\n        QString direct_dns_strategy = \"\";\n        bool dns_routing = true;\n        bool use_dns_object = false;\n        QString dns_object = \"\";\n        QString dns_final_out = \"proxy\";\n\n        // Misc\n        QString domain_strategy = \"AsIs\";\n        QString outbound_domain_strategy = \"AsIs\";\n        int sniffing_mode = SniffingMode::FOR_ROUTING;\n\n        explicit Routing(int preset = 0);\n\n        [[nodiscard]] QString DisplayRouting() const;\n\n        static QStringList List();\n\n        static bool SetToActive(const QString &name);\n    };\n\n    class ExtraCore : public JsonStore {\n    public:\n        QString core_map;\n\n        explicit ExtraCore();\n\n        [[nodiscard]] QString Get(const QString &id) const;\n\n        void Set(const QString &id, const QString &path);\n\n        void Delete(const QString &id);\n    };\n\n    class InboundAuthorization : public JsonStore {\n    public:\n        QString username;\n        QString password;\n\n        InboundAuthorization();\n\n        [[nodiscard]] bool NeedAuth() const;\n    };\n\n    class DataStore : public JsonStore {\n    public:\n        // Running\n\n        QString core_token;\n        int core_port = 19810;\n        int started_id = -1919;\n        bool core_running = false;\n        bool prepare_exit = false;\n        bool spmode_vpn = false;\n        bool spmode_system_proxy = false;\n        bool need_keep_vpn_off = false;\n        QString appdataDir = \"\";\n        QStringList ignoreConnTag = {};\n\n        std::unique_ptr<Routing> routing;\n        int imported_count = 0;\n        bool refreshing_group_list = false;\n        bool refreshing_group = false;\n        int resolve_count = 0;\n\n        // Flags\n        QStringList argv = {};\n        bool flag_use_appdata = false;\n        bool flag_many = false;\n        bool flag_tray = false;\n        bool flag_debug = false;\n        bool flag_restart_tun_on = false;\n        bool flag_reorder = false;\n\n        // Saved\n\n        // Misc\n        QString log_level = \"info\";\n        QString test_latency_url = \"http://cp.cloudflare.com/\";\n        QString test_download_url = \"http://cachefly.cachefly.net/10mb.test\";\n        int test_download_timeout = 30;\n        int test_concurrent = 5;\n        bool old_share_link_format = true;\n        int traffic_loop_interval = 1000;\n        bool connection_statistics = false;\n        int current_group = 0; // group id\n        QString mux_protocol = \"h2mux\";\n        bool mux_padding = false;\n        int mux_concurrency = 8;\n        bool mux_default_on = false;\n        QString theme = \"0\";\n        int language = 0;\n        QString mw_size = \"\";\n        bool check_include_pre = false;\n        QString system_proxy_format = \"\";\n        QStringList log_ignore = {};\n        bool start_minimal = false;\n        int max_log_line = 200;\n        QString splitter_state = \"\";\n\n        // Subscription\n        QString user_agent = \"\"; // set at main.cpp\n        bool sub_use_proxy = false;\n        bool sub_clear = false;\n        bool sub_insecure = false;\n        int sub_auto_update = -30;\n\n        // Security\n        bool skip_cert = false;\n        QString utlsFingerprint = \"\";\n\n        // Remember\n        QStringList remember_spmode = {};\n        int remember_id = -1919;\n        bool remember_enable = false;\n\n        // Socks & HTTP Inbound\n        QString inbound_address = \"127.0.0.1\";\n        int inbound_socks_port = 2080; // or Mixed\n        InboundAuthorization *inbound_auth = new InboundAuthorization;\n        QString custom_inbound = \"{\\\"inbounds\\\": []}\";\n\n        // Routing\n        QString custom_route_global = \"{\\\"rules\\\": []}\";\n        QString active_routing = \"Default\";\n\n        // VPN\n        bool fake_dns = false;\n        bool vpn_internal_tun = true;\n        int vpn_implementation = 0;\n        int vpn_mtu = 9000;\n        bool vpn_ipv6 = false;\n        bool vpn_hide_console = true;\n        bool vpn_strict_route = false;\n        bool vpn_rule_white = false;\n        QString vpn_rule_process = \"\";\n        QString vpn_rule_cidr = \"\";\n\n        // Hotkey\n        QString hotkey_mainwindow = \"\";\n        QString hotkey_group = \"\";\n        QString hotkey_route = \"\";\n        QString hotkey_system_proxy_menu = \"\";\n\n        // Core\n        int core_box_clash_api = -9090;\n        QString core_box_clash_api_secret = \"\";\n        QString core_box_underlying_dns = \"\";\n\n        // Other Core\n        ExtraCore *extraCore = new ExtraCore;\n\n        // Methods\n\n        DataStore();\n\n        void UpdateStartedId(int id);\n\n        QString GetUserAgent(bool isDefault = false) const;\n    };\n\n    extern DataStore *dataStore;\n\n} // namespace NekoGui\n"
  },
  {
    "path": "main/NekoGui_Utils.cpp",
    "content": "#include \"NekoGui_Utils.hpp\"\n\n#include \"3rdparty/base64.h\"\n#include \"3rdparty/QThreadCreateThread.hpp\"\n\n#include <random>\n\n#include <QApplication>\n#include <QUrlQuery>\n#include <QTcpServer>\n#include <QTimer>\n#include <QMessageBox>\n#include <QFile>\n#include <QJsonObject>\n#include <QJsonArray>\n#include <QJsonDocument>\n#include <QRegularExpression>\n#include <QDateTime>\n#include <QLocale>\n\n#ifdef Q_OS_WIN\n#include \"sys/windows/guihelper.h\"\n#endif\n\nQStringList SplitLines(const QString &_string) {\n#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)\n    return _string.split(QRegularExpression(\"[\\r\\n]\"), Qt::SplitBehaviorFlags::SkipEmptyParts);\n#else\n    return _string.split(QRegularExpression(\"[\\r\\n]\"), QString::SkipEmptyParts);\n#endif\n}\n\nQStringList SplitLinesSkipSharp(const QString &_string, int maxLine) {\n    auto lines = SplitLines(_string);\n    QStringList newLines;\n    int i = 0;\n    for (const auto &line: lines) {\n        if (line.trimmed().startsWith(\"#\")) continue;\n        newLines << line;\n        if (maxLine > 0 && ++i >= maxLine) break;\n    }\n    return newLines;\n}\n\nQByteArray DecodeB64IfValid(const QString &input, QByteArray::Base64Options options) {\n    Qt515Base64::Base64Options newOptions = Qt515Base64::Base64Option::AbortOnBase64DecodingErrors;\n    if (options.testFlag(QByteArray::Base64UrlEncoding)) newOptions |= Qt515Base64::Base64Option::Base64UrlEncoding;\n    if (options.testFlag(QByteArray::OmitTrailingEquals)) newOptions |= Qt515Base64::Base64Option::OmitTrailingEquals;\n    auto result = Qt515Base64::QByteArray_fromBase64Encoding(input.toUtf8(), newOptions);\n    if (result) {\n        return result.decoded;\n    }\n    return {};\n}\n\nQString QStringList2Command(const QStringList &list) {\n    QStringList new_list;\n    for (auto str: list) {\n        auto q = \"\\\"\" + str.replace(\"\\\"\", \"\\\\\\\"\") + \"\\\"\";\n        new_list << q;\n    }\n    return new_list.join(\" \");\n}\n\nQString GetQueryValue(const QUrlQuery &q, const QString &key, const QString &def) {\n    auto a = q.queryItemValue(key);\n    if (a.isEmpty()) {\n        return def;\n    }\n    return a;\n}\n\nQString GetRandomString(int randomStringLength) {\n    std::random_device rd;\n    std::mt19937 mt(rd());\n\n    const QString possibleCharacters(\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\");\n\n    std::uniform_int_distribution<int> dist(0, possibleCharacters.length() - 1);\n\n    QString randomString;\n    for (int i = 0; i < randomStringLength; ++i) {\n        QChar nextChar = possibleCharacters.at(dist(mt));\n        randomString.append(nextChar);\n    }\n    return randomString;\n}\n\nquint64 GetRandomUint64() {\n    std::random_device rd;\n    std::mt19937 mt(rd());\n    std::uniform_int_distribution<quint64> dist;\n    return dist(mt);\n}\n\n// QString >> QJson\nQJsonObject QString2QJsonObject(const QString &jsonString) {\n    QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonString.toUtf8());\n    QJsonObject jsonObject = jsonDocument.object();\n    return jsonObject;\n}\n\n// QJson >> QString\nQString QJsonObject2QString(const QJsonObject &jsonObject, bool compact) {\n    return QJsonDocument(jsonObject).toJson(compact ? QJsonDocument::Compact : QJsonDocument::Indented);\n}\n\ntemplate<typename T>\nQJsonArray QList2QJsonArray(const QList<T> &list) {\n    QVariantList list2;\n    for (auto &item: list)\n        list2.append(item);\n    return QJsonArray::fromVariantList(list2);\n}\n\ntemplate QJsonArray QList2QJsonArray<int>(const QList<int> &list);\ntemplate QJsonArray QList2QJsonArray<QString>(const QList<QString> &list);\n\nQList<int> QJsonArray2QListInt(const QJsonArray &arr) {\n    QList<int> list2;\n    for (auto item: arr)\n        list2.append(item.toInt());\n    return list2;\n}\n\nQList<QString> QJsonArray2QListString(const QJsonArray &arr) {\n    QList<QString> list2;\n    for (auto item: arr)\n        list2.append(item.toString());\n    return list2;\n}\n\nQByteArray ReadFile(const QString &path) {\n    QFile file(path);\n    file.open(QFile::ReadOnly);\n    return file.readAll();\n}\n\nQString ReadFileText(const QString &path) {\n    QFile file(path);\n    file.open(QFile::ReadOnly | QFile::Text);\n    QTextStream stream(&file);\n    return stream.readAll();\n}\n\nint MkPort() {\n    QTcpServer s;\n    s.listen();\n    auto port = s.serverPort();\n    s.close();\n    return port;\n}\n\nQString ReadableSize(const qint64 &size) {\n    double sizeAsDouble = size;\n    static QStringList measures;\n    if (measures.isEmpty())\n        measures << \"B\"\n                 << \"KiB\"\n                 << \"MiB\"\n                 << \"GiB\"\n                 << \"TiB\"\n                 << \"PiB\"\n                 << \"EiB\"\n                 << \"ZiB\"\n                 << \"YiB\";\n    QStringListIterator it(measures);\n    QString measure(it.next());\n    while (sizeAsDouble >= 1024.0 && it.hasNext()) {\n        measure = it.next();\n        sizeAsDouble /= 1024.0;\n    }\n    return QString::fromLatin1(\"%1 %2\").arg(sizeAsDouble, 0, 'f', 2).arg(measure);\n}\n\nbool IsIpAddress(const QString &str) {\n    auto address = QHostAddress(str);\n    if (address.protocol() == QAbstractSocket::IPv4Protocol || address.protocol() == QAbstractSocket::IPv6Protocol)\n        return true;\n    return false;\n}\n\nbool IsIpAddressV4(const QString &str) {\n    auto address = QHostAddress(str);\n    if (address.protocol() == QAbstractSocket::IPv4Protocol)\n        return true;\n    return false;\n}\n\nbool IsIpAddressV6(const QString &str) {\n    auto address = QHostAddress(str);\n    if (address.protocol() == QAbstractSocket::IPv6Protocol)\n        return true;\n    return false;\n}\n\nQString DisplayTime(long long time, int formatType) {\n    QDateTime t;\n    t.setMSecsSinceEpoch(time * 1000);\n    return QLocale().toString(t, QLocale::FormatType(formatType));\n}\n\nQWidget *GetMessageBoxParent() {\n    auto activeWindow = QApplication::activeWindow();\n    if (activeWindow == nullptr && mainwindow != nullptr) {\n        if (mainwindow->isVisible()) return mainwindow;\n        return nullptr;\n    }\n    return activeWindow;\n}\n\nint MessageBoxWarning(const QString &title, const QString &text) {\n    return QMessageBox::warning(GetMessageBoxParent(), title, text);\n}\n\nint MessageBoxInfo(const QString &title, const QString &text) {\n    return QMessageBox::information(GetMessageBoxParent(), title, text);\n}\n\nvoid ActivateWindow(QWidget *w) {\n    w->setWindowState(w->windowState() & ~Qt::WindowMinimized);\n    w->setVisible(true);\n#ifdef Q_OS_WIN\n    Windows_QWidget_SetForegroundWindow(w);\n#endif\n    w->raise();\n    w->activateWindow();\n}\n\nvoid runOnUiThread(const std::function<void()> &callback, QObject *parent) {\n    // any thread\n    auto *timer = new QTimer();\n    auto thread = dynamic_cast<QThread *>(parent);\n    if (thread == nullptr) {\n        timer->moveToThread(parent == nullptr ? mainwindow->thread() : parent->thread());\n    } else {\n        timer->moveToThread(thread);\n    }\n    timer->setSingleShot(true);\n    QObject::connect(timer, &QTimer::timeout, [=]() {\n        // main thread\n        callback();\n        timer->deleteLater();\n    });\n    QMetaObject::invokeMethod(timer, \"start\", Qt::QueuedConnection, Q_ARG(int, 0));\n}\n\nvoid runOnNewThread(const std::function<void()> &callback) {\n    createQThread(callback)->start();\n}\n\nvoid setTimeout(const std::function<void()> &callback, QObject *obj, int timeout) {\n    auto t = new QTimer;\n    QObject::connect(t, &QTimer::timeout, obj, [=] {\n        callback();\n        t->deleteLater();\n    });\n    t->setSingleShot(true);\n    t->setInterval(timeout);\n    t->start();\n}\n"
  },
  {
    "path": "main/NekoGui_Utils.hpp",
    "content": "// DO NOT INCLUDE THIS\n\n#include <functional>\n#include <memory>\n#include <QObject>\n#include <QString>\n#include <QDebug>\n\n//\n\ninline QString software_name = \"NekoBox\";\ninline QString software_core_name = \"sing-box\";\n\n// Main Functions\n\ninline std::function<void()> MF_release_runguard;\n\n// MainWindow functions\nclass QWidget;\ninline QWidget *mainwindow;\ninline std::function<void(QString)> MW_show_log;\ninline std::function<void(QString, QString)> MW_show_log_ext;\ninline std::function<void(QString)> MW_show_log_ext_vt100;\ninline std::function<void(QString, QString)> MW_dialog_message;\n\n// Dispatchers\n\nclass QThread;\ninline QThread *DS_cores;\n\n// Timers\n\nclass QTimer;\ninline QTimer *TM_auto_update_subsctiption;\ninline std::function<void(int)> TM_auto_update_subsctiption_Reset_Minute;\n\n// String\n\n#define FIRST_OR_SECOND(a, b) a.isEmpty() ? b : a\n\ninline const QString UNICODE_LRO = QString::fromUtf8(QByteArray::fromHex(\"E280AD\"));\n\n#define Int2String(num) QString::number(num)\n\ninline QString SubStrBefore(QString str, const QString &sub) {\n    if (!str.contains(sub)) return str;\n    return str.left(str.indexOf(sub));\n}\n\ninline QString SubStrAfter(QString str, const QString &sub) {\n    if (!str.contains(sub)) return str;\n    return str.right(str.length() - str.indexOf(sub) - sub.length());\n}\n\nQString QStringList2Command(const QStringList &list);\n\nQStringList SplitLines(const QString &_string);\n\nQStringList SplitLinesSkipSharp(const QString &_string, int maxLine = 0);\n\n// Base64\n\nQByteArray DecodeB64IfValid(const QString &input, QByteArray::Base64Options options = QByteArray::Base64Option::Base64Encoding);\n\n// URL\n\nclass QUrlQuery;\n\n#define GetQuery(url) QUrlQuery((url).query(QUrl::ComponentFormattingOption::FullyDecoded));\n\nQString GetQueryValue(const QUrlQuery &q, const QString &key, const QString &def = \"\");\n\nQString GetRandomString(int randomStringLength);\n\nquint64 GetRandomUint64();\n\n// JSON\n\nclass QJsonObject;\nclass QJsonArray;\n\nQJsonObject QString2QJsonObject(const QString &jsonString);\n\nQString QJsonObject2QString(const QJsonObject &jsonObject, bool compact);\n\ntemplate<typename T>\nQJsonArray QList2QJsonArray(const QList<T> &list);\n\nQList<int> QJsonArray2QListInt(const QJsonArray &arr);\n\n#define QJSONARRAY_ADD(arr, add) \\\n    for (const auto &a: (add)) { \\\n        (arr) += a;              \\\n    }\n#define QJSONOBJECT_COPY(src, dst, key) \\\n    if (src.contains(key)) dst[key] = src[key];\n#define QJSONOBJECT_COPY2(src, dst, src_key, dst_key) \\\n    if (src.contains(src_key)) dst[dst_key] = src[src_key];\n\nQList<QString> QJsonArray2QListString(const QJsonArray &arr);\n\n// Files\n\nQByteArray ReadFile(const QString &path);\n\nQString ReadFileText(const QString &path);\n\n// Validators\n\nbool IsIpAddress(const QString &str);\n\nbool IsIpAddressV4(const QString &str);\n\nbool IsIpAddressV6(const QString &str);\n\n// [2001:4860:4860::8888] -> 2001:4860:4860::8888\ninline QString UnwrapIPV6Host(QString &str) {\n    return str.replace(\"[\", \"\").replace(\"]\", \"\");\n}\n\n// [2001:4860:4860::8888] or 2001:4860:4860::8888 -> [2001:4860:4860::8888]\ninline QString WrapIPV6Host(QString &str) {\n    if (!IsIpAddressV6(str)) return str;\n    return \"[\" + UnwrapIPV6Host(str) + \"]\";\n}\n\ninline QString DisplayAddress(QString serverAddress, int serverPort) {\n    if (serverAddress.isEmpty() && serverPort == 0) return {};\n    return WrapIPV6Host(serverAddress) + \":\" + Int2String(serverPort);\n};\n\n// Format & Misc\n\nint MkPort();\n\nQString DisplayTime(long long time, int formatType = 0);\n\nQString ReadableSize(const qint64 &size);\n\ninline bool InRange(unsigned x, unsigned low, unsigned high) {\n    return (low <= x && x <= high);\n}\n\ninline bool IsValidPort(int port) {\n    return InRange(port, 1, 65535);\n}\n\n// UI\n\nQWidget *GetMessageBoxParent();\n\nint MessageBoxWarning(const QString &title, const QString &text);\n\nint MessageBoxInfo(const QString &title, const QString &text);\n\nvoid ActivateWindow(QWidget *w);\n\n//\n\nvoid runOnUiThread(const std::function<void()> &callback, QObject *parent = nullptr);\n\nvoid runOnNewThread(const std::function<void()> &callback);\n\ntemplate<typename EMITTER, typename SIGNAL, typename RECEIVER, typename ReceiverFunc>\ninline void connectOnce(EMITTER *emitter, SIGNAL signal, RECEIVER *receiver, ReceiverFunc f,\n                        Qt::ConnectionType connectionType = Qt::AutoConnection) {\n    auto connection = std::make_shared<QMetaObject::Connection>();\n    auto onTriggered = [connection, f](auto... arguments) {\n        std::invoke(f, arguments...);\n        QObject::disconnect(*connection);\n    };\n\n    *connection = QObject::connect(emitter, signal, receiver, onTriggered, connectionType);\n}\n\nvoid setTimeout(const std::function<void()> &callback, QObject *obj, int timeout = 0);\n"
  },
  {
    "path": "main/main.cpp",
    "content": "#include <csignal>\n\n#include <QApplication>\n#include <QDir>\n#include <QTranslator>\n#include <QMessageBox>\n#include <QStandardPaths>\n#include <QLocalSocket>\n#include <QLocalServer>\n#include <QThread>\n\n#include \"3rdparty/RunGuard.hpp\"\n#include \"main/NekoGui.hpp\"\n\n#include \"ui/mainwindow_interface.h\"\n\n#ifdef Q_OS_WIN\n#include \"sys/windows/MiniDump.h\"\n#endif\n\nvoid signal_handler(int signum) {\n    if (qApp) {\n        GetMainWindow()->on_commitDataRequest();\n        qApp->exit();\n    }\n}\n\nQTranslator* trans = nullptr;\nQTranslator* trans_qt = nullptr;\n\nvoid loadTranslate(const QString& locale) {\n    if (trans != nullptr) {\n        trans->deleteLater();\n    }\n    if (trans_qt != nullptr) {\n        trans_qt->deleteLater();\n    }\n    //\n    trans = new QTranslator;\n    trans_qt = new QTranslator;\n    QLocale::setDefault(QLocale(locale));\n    //\n    if (trans->load(\":/translations/\" + locale + \".qm\")) {\n        QCoreApplication::installTranslator(trans);\n    }\n    if (trans_qt->load(QApplication::applicationDirPath() + \"/qtbase_\" + locale + \".qm\")) {\n        QCoreApplication::installTranslator(trans_qt);\n    }\n}\n\n#define LOCAL_SERVER_PREFIX \"nekoraylocalserver-\"\n\nint main(int argc, char* argv[]) {\n    // Core dump\n#ifdef Q_OS_WIN\n    Windows_SetCrashHandler();\n#endif\n\n    // pre-init QApplication\n#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)\n    QApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton);\n#endif\n#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)\n    QApplication::setAttribute(Qt::AA_DontUseNativeDialogs);\n#endif\n    QApplication::setQuitOnLastWindowClosed(false);\n    auto preQApp = new QApplication(argc, argv);\n\n    // Clean\n    QDir::setCurrent(QApplication::applicationDirPath());\n    if (QFile::exists(\"updater.old\")) {\n        QFile::remove(\"updater.old\");\n    }\n#ifndef Q_OS_WIN\n    if (!QFile::exists(\"updater\")) {\n        QFile::link(\"launcher\", \"updater\");\n    }\n#endif\n\n    // Flags\n    NekoGui::dataStore->argv = QApplication::arguments();\n    if (NekoGui::dataStore->argv.contains(\"-many\")) NekoGui::dataStore->flag_many = true;\n    if (NekoGui::dataStore->argv.contains(\"-appdata\")) {\n        NekoGui::dataStore->flag_use_appdata = true;\n        int appdataIndex = NekoGui::dataStore->argv.indexOf(\"-appdata\");\n        if (NekoGui::dataStore->argv.size() > appdataIndex + 1 && !NekoGui::dataStore->argv.at(appdataIndex + 1).startsWith(\"-\")) {\n            NekoGui::dataStore->appdataDir = NekoGui::dataStore->argv.at(appdataIndex + 1);\n        }\n    }\n    if (NekoGui::dataStore->argv.contains(\"-tray\")) NekoGui::dataStore->flag_tray = true;\n    if (NekoGui::dataStore->argv.contains(\"-debug\")) NekoGui::dataStore->flag_debug = true;\n    if (NekoGui::dataStore->argv.contains(\"-flag_restart_tun_on\")) NekoGui::dataStore->flag_restart_tun_on = true;\n    if (NekoGui::dataStore->argv.contains(\"-flag_reorder\")) NekoGui::dataStore->flag_reorder = true;\n#ifdef NKR_CPP_USE_APPDATA\n    NekoGui::dataStore->flag_use_appdata = true; // Example: Package & MacOS\n#endif\n#ifdef NKR_CPP_DEBUG\n    NekoGui::dataStore->flag_debug = true;\n#endif\n\n    // dirs & clean\n    auto wd = QDir(QApplication::applicationDirPath());\n    if (NekoGui::dataStore->flag_use_appdata) {\n        QApplication::setApplicationName(\"nekoray\");\n        if (!NekoGui::dataStore->appdataDir.isEmpty()) {\n            wd.setPath(NekoGui::dataStore->appdataDir);\n        } else {\n            wd.setPath(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));\n        }\n    }\n    if (!wd.exists()) wd.mkpath(wd.absolutePath());\n    if (!wd.exists(\"config\")) wd.mkdir(\"config\");\n    QDir::setCurrent(wd.absoluteFilePath(\"config\"));\n    QDir(\"temp\").removeRecursively();\n\n    // init QApplication\n    delete preQApp;\n    QApplication a(argc, argv);\n\n    // dispatchers\n    DS_cores = new QThread;\n    DS_cores->start();\n\n    // RunGuard\n    RunGuard guard(\"nekoray\" + wd.absolutePath());\n    quint64 guard_data_in = GetRandomUint64();\n    quint64 guard_data_out = 0;\n    if (!NekoGui::dataStore->flag_many && !guard.tryToRun(&guard_data_in)) {\n        // Some Good System\n        if (guard.isAnotherRunning(&guard_data_out)) {\n            // Wake up a running instance\n            QLocalSocket socket;\n            socket.connectToServer(LOCAL_SERVER_PREFIX + Int2String(guard_data_out));\n            qDebug() << socket.fullServerName();\n            if (!socket.waitForConnected(500)) {\n                qDebug() << \"Failed to wake a running instance.\";\n                return 0;\n            }\n            qDebug() << \"connected to local server, try to raise another program\";\n            return 0;\n        }\n        // Some Bad System\n        QMessageBox::warning(nullptr, \"NekoGui\", \"RunGuard disallow to run, use -many to force start.\");\n        return 0;\n    }\n    MF_release_runguard = [&] { guard.release(); };\n\n// icons\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))\n    QIcon::setFallbackSearchPaths(QStringList{\n        \":/neko\",\n        \":/icon\",\n    });\n#endif\n\n    // icon for no theme\n    if (QIcon::themeName().isEmpty()) {\n        QIcon::setThemeName(\"breeze\");\n    }\n\n    // Dir\n    QDir dir;\n    bool dir_success = true;\n    if (!dir.exists(\"profiles\")) {\n        dir_success &= dir.mkdir(\"profiles\");\n    }\n    if (!dir.exists(\"groups\")) {\n        dir_success &= dir.mkdir(\"groups\");\n    }\n    if (!dir.exists(ROUTES_PREFIX_NAME)) {\n        dir_success &= dir.mkdir(ROUTES_PREFIX_NAME);\n    }\n    if (!dir_success) {\n        QMessageBox::warning(nullptr, \"Error\", \"No permission to write \" + dir.absolutePath());\n        return 1;\n    }\n\n    // Load dataStore\n    switch (NekoGui::coreType) {\n        case NekoGui::CoreType::SING_BOX:\n            NekoGui::dataStore->fn = \"groups/nekobox.json\";\n            break;\n        default:\n            MessageBoxWarning(\"Error\", \"Unknown coreType.\");\n            return 0;\n    }\n    auto isLoaded = NekoGui::dataStore->Load();\n    if (!isLoaded) {\n        NekoGui::dataStore->Save();\n    }\n\n    // Datastore & Flags\n    if (NekoGui::dataStore->start_minimal) NekoGui::dataStore->flag_tray = true;\n\n    // load routing\n    NekoGui::dataStore->routing = std::make_unique<NekoGui::Routing>();\n    NekoGui::dataStore->routing->fn = ROUTES_PREFIX + NekoGui::dataStore->active_routing;\n    isLoaded = NekoGui::dataStore->routing->Load();\n    if (!isLoaded) {\n        NekoGui::dataStore->routing->Save();\n    }\n\n    // Translate\n    QString locale;\n    switch (NekoGui::dataStore->language) {\n        case 1: // English\n            break;\n        case 2:\n            locale = \"zh_CN\";\n            break;\n        case 3:\n            locale = \"fa_IR\"; // farsi(iran)\n            break;\n        case 4:\n            locale = \"ru_RU\"; // Russian\n            break;\n        default:\n            locale = QLocale().name();\n    }\n    QGuiApplication::tr(\"QT_LAYOUT_DIRECTION\");\n    loadTranslate(locale);\n\n    // Signals\n    signal(SIGTERM, signal_handler);\n    signal(SIGINT, signal_handler);\n\n    // QLocalServer\n    QLocalServer server;\n    auto server_name = LOCAL_SERVER_PREFIX + Int2String(guard_data_in);\n    QLocalServer::removeServer(server_name);\n    server.listen(server_name);\n    QObject::connect(&server, &QLocalServer::newConnection, &a, [&] {\n        auto socket = server.nextPendingConnection();\n        qDebug() << \"nextPendingConnection:\" << server_name << socket;\n        socket->deleteLater();\n        // raise main window\n        MW_dialog_message(\"\", \"Raise\");\n    });\n\n    UI_InitMainWindow();\n    return QApplication::exec();\n}\n"
  },
  {
    "path": "nekoray_version.txt",
    "content": "4.0.1-2024-12-12\n"
  },
  {
    "path": "res/dashboard-notice.html",
    "content": "<html>\n\n<head></head>\n\n<body>\n    <h3>\n        <p>Please put your clash dashboard files to \"./config/dashboard\" dir.</p>\n        <p>For example, you can download from the following URL.</p>\n        <p>\n            <a href=\"https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip\">Download Yacd-meta</a>\n            or\n            <a href=\"http://yacd.metacubex.one/\">Use online</a>\n        </p>\n    </h3>\n</body>\n\n</html>"
  },
  {
    "path": "res/neko.css",
    "content": "QMessageBox {\n    messagebox-text-interaction-flags: 5;\n}\n"
  },
  {
    "path": "res/neko.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/\">\n        <file alias=\"icon/b-internet-web-browser.svg\">icon/internet-web-browser.svg</file>\n        <file alias=\"icon/b-system-run.svg\">icon/system-run.svg</file>\n        <file alias=\"icon/b-preferences.svg\">icon/preferences.svg</file>\n        <file alias=\"icon/b-network-server.svg\">icon/network-server.svg</file>\n        <file alias=\"icon/b-dialog-question.svg\">icon/dialog-question.svg</file>\n        <file alias=\"icon/b-system-software-update.svg\">icon/system-software-update.svg</file>\n        <file>icon/material/lock-open-outline.svg</file>\n        <file>icon/material/lock-outline.svg</file>\n        <file>icon/material/cancel.svg</file>\n        <file>icon/material/history.svg</file>\n        <file>icon/material/swap-vertical.svg</file>\n        <file>icon/material/delete.svg</file>\n        <file>icon/material/swap-horizontal.svg</file>\n    </qresource>\n    <qresource prefix=\"/neko\">\n        <file alias=\"nekobox.png\">public/nekobox.png</file>\n        <file>neko.css</file>\n        <file>vpn/vpn-run-root.sh</file>\n        <file>vpn/sing-box-vpn.json</file>\n        <file>dashboard-notice.html</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "res/theme/feiyangqingyun/qss/blacksoft.css",
    "content": "QPalette{background:#444444;}*{outline:0px;color:#DCDCDC;}\n\nQGraphicsView{\nborder:1px solid #242424;\nqproperty-backgroundBrush:#444444;\n}\n\nQWidget[form=\"true\"],QLabel[frameShape=\"1\"]{\nborder:1px solid #242424;\nborder-radius:0px;\n}\n\nQWidget[form=\"bottom\"]{\nbackground:#484848;\n}\n\nQWidget[form=\"bottom\"] .QFrame{\nborder:1px solid #DCDCDC;\n}\n\nQWidget[form=\"bottom\"] QLabel,QWidget[form=\"title\"] QLabel{\nborder-radius:0px;\ncolor:#DCDCDC;\nbackground:none;\nborder-style:none;\n}\n\nQWidget[form=\"title\"],QWidget[nav=\"left\"],QWidget[nav=\"top\"] QAbstractButton{\nborder-style:none;\nborder-radius:0px;\npadding:5px;\ncolor:#DCDCDC;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\nQWidget[nav=\"top\"] QAbstractButton:hover,QWidget[nav=\"top\"] QAbstractButton:pressed,QWidget[nav=\"top\"] QAbstractButton:checked{\nborder-style:solid;\nborder-width:0px 0px 2px 0px;\npadding:4px 4px 2px 4px;\nborder-color:#AAAAAA;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);\n}\n\nQWidget[nav=\"left\"] QAbstractButton{\nborder-radius:0px;\ncolor:#DCDCDC;\nbackground:none;\nborder-style:none;\n}\n\nQWidget[nav=\"left\"] QAbstractButton:hover{\ncolor:#FFFFFF;\nbackground-color:#AAAAAA;\n}\n\nQWidget[nav=\"left\"] QAbstractButton:checked,QWidget[nav=\"left\"] QAbstractButton:pressed{\ncolor:#DCDCDC;\nborder-style:solid;\nborder-width:0px 0px 0px 2px;\npadding:4px 4px 4px 2px;\nborder-color:#AAAAAA;\nbackground-color:#444444;\n}\n\nQWidget[video=\"true\"] QLabel{\ncolor:#DCDCDC;\nborder:1px solid #242424;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\nQWidget[video=\"true\"] QLabel:focus{\nborder:1px solid #AAAAAA;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);\n}\n\nQLineEdit:read-only{\nbackground-color:#484848;\n}\n\nQLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{\nborder:1px solid #242424;\nborder-radius:3px;\npadding:2px;\nbackground:none;\nselection-background-color:#AAAAAA;\nselection-color:#FFFFFF;\n}\n\nQLineEdit:focus,QTextEdit:focus,QPlainTextEdit:focus,QSpinBox:focus,QDoubleSpinBox:focus,QComboBox:focus,QDateEdit:focus,QTimeEdit:focus,QDateTimeEdit:focus,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QSpinBox:hover,QDoubleSpinBox:hover,QComboBox:hover,QDateEdit:hover,QTimeEdit:hover,QDateTimeEdit:hover{\nborder:1px solid #242424;\n}\n\nQLineEdit[echoMode=\"2\"]{\nlineedit-password-character:9679;\n}\n\n.QFrame{\nborder:1px solid #242424;\nborder-radius:3px;\n}\n\n.QGroupBox{\nborder:1px solid #242424;\nborder-radius:5px;\nmargin-top:3ex;\n}\n\n.QGroupBox::title{\nsubcontrol-origin:margin;\nposition:relative;\nleft:10px;\n}\n\n.QPushButton,.QToolButton{\nborder-style:none;\nborder:1px solid #242424;\ncolor:#DCDCDC;\npadding:5px;\nmin-height:15px;\nborder-radius:5px;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\n.QPushButton:hover,.QToolButton:hover{\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);\n}\n\n.QPushButton:pressed,.QToolButton:pressed{\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\n.QToolButton::menu-indicator{\nimage:None;\n}\n\nQToolButton#btnMenu,QPushButton#btnMenu_Min,QPushButton#btnMenu_Max,QPushButton#btnMenu_Close{\nborder-radius:3px;\ncolor:#DCDCDC;\npadding:3px;\nmargin:0px;\nbackground:none;\nborder-style:none;\n}\n\nQToolButton#btnMenu:hover,QPushButton#btnMenu_Min:hover,QPushButton#btnMenu_Max:hover{\ncolor:#FFFFFF;\nmargin:1px 1px 2px 1px;\nbackground-color:rgba(51,127,209,230);\n}\n\nQPushButton#btnMenu_Close:hover{\ncolor:#FFFFFF;\nmargin:1px 1px 2px 1px;\nbackground-color:rgba(238,0,0,128);\n}\n\nQRadioButton::indicator{\nwidth:15px;\nheight:15px;\n}\n\nQRadioButton::indicator::unchecked{\nimage:url(:/qss/blacksoft/radiobutton_unchecked.png);\n}\n\nQRadioButton::indicator::unchecked:disabled{\nimage:url(:/qss/blacksoft/radiobutton_unchecked_disable.png);\n}\n\nQRadioButton::indicator::checked{\nimage:url(:/qss/blacksoft/radiobutton_checked.png);\n}\n\nQRadioButton::indicator::checked:disabled{\nimage:url(:/qss/blacksoft/radiobutton_checked_disable.png);\n}\n\nQGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{\npadding:0px 0px 0px 0px;\n}\n\nQCheckBox::indicator,QGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{\nwidth:13px;\nheight:13px;\n}\n\nQCheckBox::indicator:unchecked,QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QListView::indicator:unchecked,QTableView::indicator:unchecked{\nimage:url(:/qss/blacksoft/checkbox_unchecked.png);\n}\n\nQCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QListView::indicator:unchecked:disabled,QTableView::indicator:unchecked:disabled{\nimage:url(:/qss/blacksoft/checkbox_unchecked_disable.png);\n}\n\nQCheckBox::indicator:checked,QGroupBox::indicator:checked,QTreeView::indicator:checked,QListView::indicator:checked,QTableView::indicator:checked{\nimage:url(:/qss/blacksoft/checkbox_checked.png);\n}\n\nQCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,QTreeView::indicator:checked:disabled,QListView::indicator:checked:disabled,QTableView::indicator:checked:disabled{\nimage:url(:/qss/blacksoft/checkbox_checked_disable.png);\n}\n\nQCheckBox::indicator:indeterminate,QGroupBox::indicator:indeterminate,QTreeView::indicator:indeterminate,QListView::indicator:indeterminate,QTableView::indicator:indeterminate{\nimage:url(:/qss/blacksoft/checkbox_parcial.png);\n}\n\nQCheckBox::indicator:indeterminate:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:indeterminate:disabled,QListView::indicator:indeterminate:disabled,QTableView::indicator:indeterminate:disabled{\nimage:url(:/qss/blacksoft/checkbox_parcial_disable.png);\n}\n\nQTimeEdit::up-button,QDateEdit::up-button,QDateTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button{\nimage:url(:/qss/blacksoft/add_top.png);\nwidth:10px;\nheight:10px;\npadding:2px 5px 0px 0px;\n}\n\nQTimeEdit::down-button,QDateEdit::down-button,QDateTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button{\nimage:url(:/qss/blacksoft/add_bottom.png);\nwidth:10px;\nheight:10px;\npadding:0px 5px 2px 0px;\n}\n\nQTimeEdit::up-button:pressed,QDateEdit::up-button:pressed,QDateTimeEdit::up-button:pressed,QDoubleSpinBox::up-button:pressed,QSpinBox::up-button:pressed{\ntop:-2px;\n}\n  \nQTimeEdit::down-button:pressed,QDateEdit::down-button:pressed,QDateTimeEdit::down-button:pressed,QDoubleSpinBox::down-button:pressed,QSpinBox::down-button:pressed,QSpinBox::down-button:pressed{\nbottom:-2px;\n}\n\nQComboBox::down-arrow,QDateEdit[calendarPopup=\"true\"]::down-arrow,QTimeEdit[calendarPopup=\"true\"]::down-arrow,QDateTimeEdit[calendarPopup=\"true\"]::down-arrow{\nimage:url(:/qss/blacksoft/add_bottom.png);\nwidth:10px;\nheight:10px;\nright:2px;\n}\n\nQComboBox::drop-down,QDateEdit::drop-down,QTimeEdit::drop-down,QDateTimeEdit::drop-down{\nsubcontrol-origin:padding;\nsubcontrol-position:top right;\nwidth:15px;\nborder-left-width:0px;\nborder-left-style:solid;\nborder-top-right-radius:3px;\nborder-bottom-right-radius:3px;\nborder-left-color:#242424;\n}\n\nQComboBox::drop-down:on{\ntop:1px;\n}\n\nQMenuBar::item{\ncolor:#DCDCDC;\nbackground-color:#484848;\nmargin:0px;\npadding:3px 10px;\n}\n\nQMenu,QMenuBar,QMenu:disabled,QMenuBar:disabled{\ncolor:#DCDCDC;\nbackground-color:#484848;\nborder:1px solid #242424;\nmargin:0px;\n}\n\nQMenu::item{\npadding:3px 20px;\n}\n\nQMenu::indicator{\nwidth:20px;\nheight:13px;\n}\n\nQMenu::indicator::checked{\nimage:url(:/qss/blacksoft/menu_checked.png);\n}\n\nQMenu::right-arrow{\nimage:url(:/qss/blacksoft/arrow_right.png);\nwidth:13px;\nheight:13px;\npadding:0px 3px 0px 0px;\n}\n\nQMenu::item:selected,QMenuBar::item:selected{\ncolor:#DCDCDC;\nborder:0px solid #242424;\nbackground:#646464;\n}\n\nQMenu::separator{\nheight:1px;\nbackground:#242424;\n}\n\nQProgressBar{\nmin-height:10px;\nbackground:#484848;\nborder-radius:5px;\ntext-align:center;\nborder:1px solid #484848;\n}\n\nQProgressBar:chunk{\nborder-radius:5px;\nbackground-color:#242424;\n}\n\nQSlider::groove:horizontal{\nheight:8px;\nborder-radius:4px;\nbackground:#484848;\n}\n\nQSlider::add-page:horizontal{\nheight:8px;\nborder-radius:4px;\nbackground:#484848;\n}\n\nQSlider::sub-page:horizontal{\nheight:8px;\nborder-radius:4px;\nbackground:#242424;\n}\n\nQSlider::handle:horizontal{\nwidth:13px;\nmargin-top:-3px;\nmargin-bottom:-3px;\nborder-radius:6px;\nbackground:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #444444,stop:0.8 #242424);\n}\n\nQSlider::groove:vertical{\nwidth:8px;\nborder-radius:4px;\nbackground:#484848;\n}\n\nQSlider::add-page:vertical{\nwidth:8px;\nborder-radius:4px;\nbackground:#242424;\n}\n\nQSlider::sub-page:vertical{\nwidth:8px;\nborder-radius:4px;\nbackground:#484848;\n}\n\nQSlider::handle:vertical{\nheight:14px;\nmargin-left:-3px;\nmargin-right:-3px;\nborder-radius:6px;\nbackground:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #444444,stop:0.8 #242424);\n}\n\nQScrollBar:horizontal{\nbackground:#484848;\npadding:0px;\nborder-radius:6px;\nmax-height:12px;\n}\n\nQScrollBar::handle:horizontal{\nbackground:#242424;\nmin-width:50px;\nborder-radius:6px;\n}\n\nQScrollBar::handle:horizontal:hover{\nbackground:#AAAAAA;\n}\n\nQScrollBar::handle:horizontal:pressed{\nbackground:#AAAAAA;\n}\n\nQScrollBar::add-page:horizontal{\nbackground:none;\n}\n\nQScrollBar::sub-page:horizontal{\nbackground:none;\n}\n\nQScrollBar::add-line:horizontal{\nbackground:none;\n}\n\nQScrollBar::sub-line:horizontal{\nbackground:none;\n}\n\nQScrollBar:vertical{\nbackground:#484848;\npadding:0px;\nborder-radius:6px;\nmax-width:12px;\n}\n\nQScrollBar::handle:vertical{\nbackground:#242424;\nmin-height:50px;\nborder-radius:6px;\n}\n\nQScrollBar::handle:vertical:hover{\nbackground:#AAAAAA;\n}\n\nQScrollBar::handle:vertical:pressed{\nbackground:#AAAAAA;\n}\n\nQScrollBar::add-page:vertical{\nbackground:none;\n}\n\nQScrollBar::sub-page:vertical{\nbackground:none;\n}\n\nQScrollBar::add-line:vertical{\nbackground:none;\n}\n\nQScrollBar::sub-line:vertical{\nbackground:none;\n}\n\nQScrollArea{\nborder:0px;\n}\n\nQTreeView,QListView,QTableView,QTabWidget::pane{\nborder:1px solid #242424;\nselection-background-color:#646464;\nselection-color:#DCDCDC;\nalternate-background-color:#525252;\ngridline-color:#242424;\n}\n\nQTreeView::branch:closed:has-children{\nmargin:4px;\nborder-image:url(:/qss/blacksoft/branch_open.png);\n}\n\nQTreeView::branch:open:has-children{\nmargin:4px;\nborder-image:url(:/qss/blacksoft/branch_close.png);\n}\n\nQTreeView,QListView,QTableView,QSplitter::handle,QTreeView::branch{\nbackground:#444444;\n}\n\nQTableView::item:selected,QListView::item:selected,QTreeView::item:selected{\ncolor:#DCDCDC;\nbackground:#383838;\n}\n\nQTableView::item:hover,QListView::item:hover,QTreeView::item:hover,QHeaderView,QHeaderView::section,QTableCornerButton:section{\ncolor:#DCDCDC;\nbackground:#525252;\n}\n\nQTableView::item,QListView::item,QTreeView::item{\npadding:1px;\nmargin:0px;\nborder:0px;\n}\n\nQHeaderView::section,QTableCornerButton:section{\npadding:3px;\nmargin:0px;\nborder:1px solid #242424;\nborder-left-width:0px;\nborder-right-width:1px;\nborder-top-width:0px;\nborder-bottom-width:1px;\n}\n\nQTabBar::tab{\nborder:1px solid #242424;\ncolor:#DCDCDC;\nmargin:0px;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);\n}\n\nQTabBar::tab:selected{\nborder-style:solid;\nborder-color:#AAAAAA;\nbackground:#444444;\n}\n\nQTabBar::tab:top,QTabBar::tab:bottom{\npadding:3px 8px 3px 8px;\n}\n\nQTabBar::tab:left,QTabBar::tab:right{\npadding:8px 3px 8px 3px;\n}\n\nQTabBar::tab:top:selected{\nborder-width:2px 0px 0px 0px;\n}\n\nQTabBar::tab:right:selected{\nborder-width:0px 0px 0px 2px;\n}\n\nQTabBar::tab:bottom:selected{\nborder-width:0px 0px 2px 0px;\n}\n\nQTabBar::tab:left:selected{\nborder-width:0px 2px 0px 0px;\n}\n\nQTabBar::tab:first:top:selected,QTabBar::tab:first:bottom:selected{\nborder-left-width:1px;\nborder-left-color:#242424;\n}\n\nQTabBar::tab:first:left:selected,QTabBar::tab:first:right:selected{\nborder-top-width:1px;\nborder-top-color:#242424;\n}\n\nQTabBar::tab:last:top:selected,QTabBar::tab:last:bottom:selected{\nborder-right-width:1px;\nborder-right-color:#242424;\n}\n\nQTabBar::tab:last:left:selected,QTabBar::tab:last:right:selected{\nborder-bottom-width:1px;\nborder-bottom-color:#242424;\n}\n\nQStatusBar::item{\nborder:0px solid #484848;\nborder-radius:3px;\n}\n\nQToolBox::tab,QGroupBox#gboxDevicePanel,QGroupBox#gboxDeviceTitle,QFrame#gboxDevicePanel,QFrame#gboxDeviceTitle{\npadding:3px;\nborder-radius:5px;\ncolor:#DCDCDC;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\nQToolTip{\nborder:0px solid #DCDCDC;\npadding:1px;\ncolor:#DCDCDC;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\nQToolBox::tab:selected{\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);\n}\n\nQPrintPreviewDialog QToolButton{\nborder:0px solid #DCDCDC;\nborder-radius:0px;\nmargin:0px;\npadding:3px;\nbackground:none;\n}\n\nQColorDialog QPushButton,QFileDialog QPushButton{\nmin-width:80px;\n}\n\nQToolButton#qt_calendar_prevmonth{\nicon-size:0px;\nmin-width:20px;\nimage:url(:/qss/blacksoft/calendar_prevmonth.png);\n}\n\nQToolButton#qt_calendar_nextmonth{\nicon-size:0px;\nmin-width:20px;\nimage:url(:/qss/blacksoft/calendar_nextmonth.png);\n}\n\nQToolButton#qt_calendar_prevmonth,QToolButton#qt_calendar_nextmonth,QToolButton#qt_calendar_monthbutton,QToolButton#qt_calendar_yearbutton{\nborder:0px solid #DCDCDC;\nborder-radius:3px;\nmargin:3px 3px 3px 3px;\npadding:3px;\nbackground:none;\n}\n\nQToolButton#qt_calendar_prevmonth:hover,QToolButton#qt_calendar_nextmonth:hover,QToolButton#qt_calendar_monthbutton:hover,QToolButton#qt_calendar_yearbutton:hover,QToolButton#qt_calendar_prevmonth:pressed,QToolButton#qt_calendar_nextmonth:pressed,QToolButton#qt_calendar_monthbutton:pressed,QToolButton#qt_calendar_yearbutton:pressed{\nborder:1px solid #242424;\n}\n\nQCalendarWidget QSpinBox#qt_calendar_yearedit{\nmargin:2px;\n}\n\nQCalendarWidget QToolButton::menu-indicator{\nimage:None;\n}\n\nQCalendarWidget QTableView{\nborder-width:0px;\n}\n\nQCalendarWidget QWidget#qt_calendar_navigationbar{\nborder:1px solid #242424;\nborder-width:1px 1px 0px 1px;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\nQTableView[model=\"true\"]::item{\npadding:0px;\nmargin:0px;\n}\n\nQTableView QLineEdit,QTableView QComboBox,QTableView QSpinBox,QTableView QDoubleSpinBox,QTableView QDateEdit,QTableView QTimeEdit,QTableView QDateTimeEdit{\nborder-width:0px;\nborder-radius:0px;\n}\n\nQTableView QLineEdit:focus,QTableView QComboBox:focus,QTableView QSpinBox:focus,QTableView QDoubleSpinBox:focus,QTableView QDateEdit:focus,QTableView QTimeEdit:focus,QTableView QDateTimeEdit:focus{\nborder-width:0px;\nborder-radius:0px;\n}\n\nQLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{\nbackground:#444444;\n}\n\nQTabWidget::pane:top{top:-1px;}\nQTabWidget::pane:bottom{bottom:-1px;}\nQTabWidget::pane:left{right:-1px;}\nQTabWidget::pane:right{left:-1px;}\n\nQDialog,QDial,#QUIWidgetMain{\nbackground-color:#444444;\ncolor:#DCDCDC;\n}\n\nQDialogButtonBox>QPushButton{\nmin-width:50px;\n}\n\nQListView[noborder=\"true\"],QTreeView[noborder=\"true\"],QTabWidget[noborder=\"true\"]::pane{\nborder-width:0px;\n}\n\nQToolBar>*,QStatusBar>*{\nmargin:2px;\n}\n\n*:disabled,QMenu::item:disabled,QTabBar:tab:disabled,QHeaderView::section:disabled{\nbackground:#444444;\nborder-color:#484848;\ncolor:#242424;\n}\n\n/*TextColor:#DCDCDC*/\n/*PanelColor:#444444*/\n/*BorderColor:#242424*/\n/*NormalColorStart:#484848*/\n/*NormalColorEnd:#383838*/\n/*DarkColorStart:#646464*/\n/*DarkColorEnd:#525252*/\n/*HighColor:#AAAAAA*/"
  },
  {
    "path": "res/theme/feiyangqingyun/qss/flatgray.css",
    "content": "QPalette{background:#FFFFFF;}*{outline:0px;color:#57595B;}\n\nQGraphicsView{\nborder:1px solid #B6B6B6;\nqproperty-backgroundBrush:#FFFFFF;\n}\n\nQWidget[form=\"true\"],QLabel[frameShape=\"1\"]{\nborder:1px solid #B6B6B6;\nborder-radius:0px;\n}\n\nQWidget[form=\"bottom\"]{\nbackground:#E4E4E4;\n}\n\nQWidget[form=\"bottom\"] .QFrame{\nborder:1px solid #57595B;\n}\n\nQWidget[form=\"bottom\"] QLabel,QWidget[form=\"title\"] QLabel{\nborder-radius:0px;\ncolor:#57595B;\nbackground:none;\nborder-style:none;\n}\n\nQWidget[form=\"title\"],QWidget[nav=\"left\"],QWidget[nav=\"top\"] QAbstractButton{\nborder-style:none;\nborder-radius:0px;\npadding:5px;\ncolor:#57595B;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);\n}\n\nQWidget[nav=\"top\"] QAbstractButton:hover,QWidget[nav=\"top\"] QAbstractButton:pressed,QWidget[nav=\"top\"] QAbstractButton:checked{\nborder-style:solid;\nborder-width:0px 0px 2px 0px;\npadding:4px 4px 2px 4px;\nborder-color:#575959;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6);\n}\n\nQWidget[nav=\"left\"] QAbstractButton{\nborder-radius:0px;\ncolor:#57595B;\nbackground:none;\nborder-style:none;\n}\n\nQWidget[nav=\"left\"] QAbstractButton:hover{\ncolor:#FFFFFF;\nbackground-color:#575959;\n}\n\nQWidget[nav=\"left\"] QAbstractButton:checked,QWidget[nav=\"left\"] QAbstractButton:pressed{\ncolor:#57595B;\nborder-style:solid;\nborder-width:0px 0px 0px 2px;\npadding:4px 4px 4px 2px;\nborder-color:#575959;\nbackground-color:#FFFFFF;\n}\n\nQWidget[video=\"true\"] QLabel{\ncolor:#57595B;\nborder:1px solid #B6B6B6;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);\n}\n\nQWidget[video=\"true\"] QLabel:focus{\nborder:1px solid #575959;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6);\n}\n\nQLineEdit:read-only{\nbackground-color:#E4E4E4;\n}\n\nQLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{\nborder:1px solid #B6B6B6;\nborder-radius:3px;\npadding:2px;\nbackground:none;\nselection-background-color:#575959;\nselection-color:#FFFFFF;\n}\n\nQLineEdit:focus,QTextEdit:focus,QPlainTextEdit:focus,QSpinBox:focus,QDoubleSpinBox:focus,QComboBox:focus,QDateEdit:focus,QTimeEdit:focus,QDateTimeEdit:focus,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QSpinBox:hover,QDoubleSpinBox:hover,QComboBox:hover,QDateEdit:hover,QTimeEdit:hover,QDateTimeEdit:hover{\nborder:1px solid #B6B6B6;\n}\n\nQLineEdit[echoMode=\"2\"]{\nlineedit-password-character:9679;\n}\n\n.QFrame{\nborder:1px solid #B6B6B6;\nborder-radius:3px;\n}\n\n.QGroupBox{\nborder:1px solid #B6B6B6;\nborder-radius:5px;\nmargin-top:3ex;\n}\n\n.QGroupBox::title{\nsubcontrol-origin:margin;\nposition:relative;\nleft:10px;\n}\n\n.QPushButton,.QToolButton{\nborder-style:none;\nborder:1px solid #B6B6B6;\ncolor:#57595B;\npadding:5px;\nmin-height:15px;\nborder-radius:5px;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);\n}\n\n.QPushButton:hover,.QToolButton:hover{\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6);\n}\n\n.QPushButton:pressed,.QToolButton:pressed{\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);\n}\n\n.QToolButton::menu-indicator{\nimage:None;\n}\n\nQToolButton#btnMenu,QPushButton#btnMenu_Min,QPushButton#btnMenu_Max,QPushButton#btnMenu_Close{\nborder-radius:3px;\ncolor:#57595B;\npadding:3px;\nmargin:0px;\nbackground:none;\nborder-style:none;\n}\n\nQToolButton#btnMenu:hover,QPushButton#btnMenu_Min:hover,QPushButton#btnMenu_Max:hover{\ncolor:#FFFFFF;\nmargin:1px 1px 2px 1px;\nbackground-color:rgba(51,127,209,230);\n}\n\nQPushButton#btnMenu_Close:hover{\ncolor:#FFFFFF;\nmargin:1px 1px 2px 1px;\nbackground-color:rgba(238,0,0,128);\n}\n\nQRadioButton::indicator{\nwidth:15px;\nheight:15px;\n}\n\nQRadioButton::indicator::unchecked{\nimage:url(:/qss/flatgray/radiobutton_unchecked.png);\n}\n\nQRadioButton::indicator::unchecked:disabled{\nimage:url(:/qss/flatgray/radiobutton_unchecked_disable.png);\n}\n\nQRadioButton::indicator::checked{\nimage:url(:/qss/flatgray/radiobutton_checked.png);\n}\n\nQRadioButton::indicator::checked:disabled{\nimage:url(:/qss/flatgray/radiobutton_checked_disable.png);\n}\n\nQGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{\npadding:0px 0px 0px 0px;\n}\n\nQCheckBox::indicator,QGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{\nwidth:13px;\nheight:13px;\n}\n\nQCheckBox::indicator:unchecked,QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QListView::indicator:unchecked,QTableView::indicator:unchecked{\nimage:url(:/qss/flatgray/checkbox_unchecked.png);\n}\n\nQCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QListView::indicator:unchecked:disabled,QTableView::indicator:unchecked:disabled{\nimage:url(:/qss/flatgray/checkbox_unchecked_disable.png);\n}\n\nQCheckBox::indicator:checked,QGroupBox::indicator:checked,QTreeView::indicator:checked,QListView::indicator:checked,QTableView::indicator:checked{\nimage:url(:/qss/flatgray/checkbox_checked.png);\n}\n\nQCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,QTreeView::indicator:checked:disabled,QListView::indicator:checked:disabled,QTableView::indicator:checked:disabled{\nimage:url(:/qss/flatgray/checkbox_checked_disable.png);\n}\n\nQCheckBox::indicator:indeterminate,QGroupBox::indicator:indeterminate,QTreeView::indicator:indeterminate,QListView::indicator:indeterminate,QTableView::indicator:indeterminate{\nimage:url(:/qss/flatgray/checkbox_parcial.png);\n}\n\nQCheckBox::indicator:indeterminate:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:indeterminate:disabled,QListView::indicator:indeterminate:disabled,QTableView::indicator:indeterminate:disabled{\nimage:url(:/qss/flatgray/checkbox_parcial_disable.png);\n}\n\nQTimeEdit::up-button,QDateEdit::up-button,QDateTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button{\nimage:url(:/qss/flatgray/add_top.png);\nwidth:10px;\nheight:10px;\npadding:2px 5px 0px 0px;\n}\n\nQTimeEdit::down-button,QDateEdit::down-button,QDateTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button{\nimage:url(:/qss/flatgray/add_bottom.png);\nwidth:10px;\nheight:10px;\npadding:0px 5px 2px 0px;\n}\n\nQTimeEdit::up-button:pressed,QDateEdit::up-button:pressed,QDateTimeEdit::up-button:pressed,QDoubleSpinBox::up-button:pressed,QSpinBox::up-button:pressed{\ntop:-2px;\n}\n  \nQTimeEdit::down-button:pressed,QDateEdit::down-button:pressed,QDateTimeEdit::down-button:pressed,QDoubleSpinBox::down-button:pressed,QSpinBox::down-button:pressed,QSpinBox::down-button:pressed{\nbottom:-2px;\n}\n\nQComboBox::down-arrow,QDateEdit[calendarPopup=\"true\"]::down-arrow,QTimeEdit[calendarPopup=\"true\"]::down-arrow,QDateTimeEdit[calendarPopup=\"true\"]::down-arrow{\nimage:url(:/qss/flatgray/add_bottom.png);\nwidth:10px;\nheight:10px;\nright:2px;\n}\n\nQComboBox::drop-down,QDateEdit::drop-down,QTimeEdit::drop-down,QDateTimeEdit::drop-down{\nsubcontrol-origin:padding;\nsubcontrol-position:top right;\nwidth:15px;\nborder-left-width:0px;\nborder-left-style:solid;\nborder-top-right-radius:3px;\nborder-bottom-right-radius:3px;\nborder-left-color:#B6B6B6;\n}\n\nQComboBox::drop-down:on{\ntop:1px;\n}\n\nQMenuBar::item{\ncolor:#57595B;\nbackground-color:#E4E4E4;\nmargin:0px;\npadding:3px 10px;\n}\n\nQMenu,QMenuBar,QMenu:disabled,QMenuBar:disabled{\ncolor:#57595B;\nbackground-color:#E4E4E4;\nborder:1px solid #B6B6B6;\nmargin:0px;\n}\n\nQMenu::item{\npadding:3px 20px;\n}\n\nQMenu::indicator{\nwidth:20px;\nheight:13px;\n}\n\nQMenu::indicator::checked{\nimage:url(:/qss/flatgray/menu_checked.png);\n}\n\nQMenu::right-arrow{\nimage:url(:/qss/flatgray/arrow_right.png);\nwidth:13px;\nheight:13px;\npadding:0px 3px 0px 0px;\n}\n\nQMenu::item:selected,QMenuBar::item:selected{\ncolor:#57595B;\nborder:0px solid #B6B6B6;\nbackground:#F6F6F6;\n}\n\nQMenu::separator{\nheight:1px;\nbackground:#B6B6B6;\n}\n\nQProgressBar{\nmin-height:10px;\nbackground:#E4E4E4;\nborder-radius:5px;\ntext-align:center;\nborder:1px solid #E4E4E4;\n}\n\nQProgressBar:chunk{\nborder-radius:5px;\nbackground-color:#B6B6B6;\n}\n\nQSlider::groove:horizontal{\nheight:8px;\nborder-radius:4px;\nbackground:#E4E4E4;\n}\n\nQSlider::add-page:horizontal{\nheight:8px;\nborder-radius:4px;\nbackground:#E4E4E4;\n}\n\nQSlider::sub-page:horizontal{\nheight:8px;\nborder-radius:4px;\nbackground:#B6B6B6;\n}\n\nQSlider::handle:horizontal{\nwidth:13px;\nmargin-top:-3px;\nmargin-bottom:-3px;\nborder-radius:6px;\nbackground:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #FFFFFF,stop:0.8 #B6B6B6);\n}\n\nQSlider::groove:vertical{\nwidth:8px;\nborder-radius:4px;\nbackground:#E4E4E4;\n}\n\nQSlider::add-page:vertical{\nwidth:8px;\nborder-radius:4px;\nbackground:#B6B6B6;\n}\n\nQSlider::sub-page:vertical{\nwidth:8px;\nborder-radius:4px;\nbackground:#E4E4E4;\n}\n\nQSlider::handle:vertical{\nheight:14px;\nmargin-left:-3px;\nmargin-right:-3px;\nborder-radius:6px;\nbackground:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #FFFFFF,stop:0.8 #B6B6B6);\n}\n\nQScrollBar:horizontal{\nbackground:#E4E4E4;\npadding:0px;\nborder-radius:6px;\nmax-height:12px;\n}\n\nQScrollBar::handle:horizontal{\nbackground:#B6B6B6;\nmin-width:50px;\nborder-radius:6px;\n}\n\nQScrollBar::handle:horizontal:hover{\nbackground:#575959;\n}\n\nQScrollBar::handle:horizontal:pressed{\nbackground:#575959;\n}\n\nQScrollBar::add-page:horizontal{\nbackground:none;\n}\n\nQScrollBar::sub-page:horizontal{\nbackground:none;\n}\n\nQScrollBar::add-line:horizontal{\nbackground:none;\n}\n\nQScrollBar::sub-line:horizontal{\nbackground:none;\n}\n\nQScrollBar:vertical{\nbackground:#E4E4E4;\npadding:0px;\nborder-radius:6px;\nmax-width:12px;\n}\n\nQScrollBar::handle:vertical{\nbackground:#B6B6B6;\nmin-height:50px;\nborder-radius:6px;\n}\n\nQScrollBar::handle:vertical:hover{\nbackground:#575959;\n}\n\nQScrollBar::handle:vertical:pressed{\nbackground:#575959;\n}\n\nQScrollBar::add-page:vertical{\nbackground:none;\n}\n\nQScrollBar::sub-page:vertical{\nbackground:none;\n}\n\nQScrollBar::add-line:vertical{\nbackground:none;\n}\n\nQScrollBar::sub-line:vertical{\nbackground:none;\n}\n\nQScrollArea{\nborder:0px;\n}\n\nQTreeView,QListView,QTableView,QTabWidget::pane{\nborder:1px solid #B6B6B6;\nselection-background-color:#F6F6F6;\nselection-color:#57595B;\nalternate-background-color:#F6F6F6;\ngridline-color:#B6B6B6;\n}\n\nQTreeView::branch:closed:has-children{\nmargin:4px;\nborder-image:url(:/qss/flatgray/branch_open.png);\n}\n\nQTreeView::branch:open:has-children{\nmargin:4px;\nborder-image:url(:/qss/flatgray/branch_close.png);\n}\n\nQTreeView,QListView,QTableView,QSplitter::handle,QTreeView::branch{\nbackground:#FFFFFF;\n}\n\nQTableView::item:selected,QListView::item:selected,QTreeView::item:selected{\ncolor:#57595B;\nbackground:#E4E4E4;\n}\n\nQTableView::item:hover,QListView::item:hover,QTreeView::item:hover,QHeaderView,QHeaderView::section,QTableCornerButton:section{\ncolor:#57595B;\nbackground:#F6F6F6;\n}\n\nQTableView::item,QListView::item,QTreeView::item{\npadding:1px;\nmargin:0px;\nborder:0px;\n}\n\nQHeaderView::section,QTableCornerButton:section{\npadding:3px;\nmargin:0px;\nborder:1px solid #B6B6B6;\nborder-left-width:0px;\nborder-right-width:1px;\nborder-top-width:0px;\nborder-bottom-width:1px;\n}\n\nQTabBar::tab{\nborder:1px solid #B6B6B6;\ncolor:#57595B;\nmargin:0px;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6);\n}\n\nQTabBar::tab:selected{\nborder-style:solid;\nborder-color:#575959;\nbackground:#FFFFFF;\n}\n\nQTabBar::tab:top,QTabBar::tab:bottom{\npadding:3px 8px 3px 8px;\n}\n\nQTabBar::tab:left,QTabBar::tab:right{\npadding:8px 3px 8px 3px;\n}\n\nQTabBar::tab:top:selected{\nborder-width:2px 0px 0px 0px;\n}\n\nQTabBar::tab:right:selected{\nborder-width:0px 0px 0px 2px;\n}\n\nQTabBar::tab:bottom:selected{\nborder-width:0px 0px 2px 0px;\n}\n\nQTabBar::tab:left:selected{\nborder-width:0px 2px 0px 0px;\n}\n\nQTabBar::tab:first:top:selected,QTabBar::tab:first:bottom:selected{\nborder-left-width:1px;\nborder-left-color:#B6B6B6;\n}\n\nQTabBar::tab:first:left:selected,QTabBar::tab:first:right:selected{\nborder-top-width:1px;\nborder-top-color:#B6B6B6;\n}\n\nQTabBar::tab:last:top:selected,QTabBar::tab:last:bottom:selected{\nborder-right-width:1px;\nborder-right-color:#B6B6B6;\n}\n\nQTabBar::tab:last:left:selected,QTabBar::tab:last:right:selected{\nborder-bottom-width:1px;\nborder-bottom-color:#B6B6B6;\n}\n\nQStatusBar::item{\nborder:0px solid #E4E4E4;\nborder-radius:3px;\n}\n\nQToolBox::tab,QGroupBox#gboxDevicePanel,QGroupBox#gboxDeviceTitle,QFrame#gboxDevicePanel,QFrame#gboxDeviceTitle{\npadding:3px;\nborder-radius:5px;\ncolor:#57595B;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);\n}\n\nQToolTip{\nborder:0px solid #57595B;\npadding:1px;\ncolor:#57595B;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);\n}\n\nQToolBox::tab:selected{\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6);\n}\n\nQPrintPreviewDialog QToolButton{\nborder:0px solid #57595B;\nborder-radius:0px;\nmargin:0px;\npadding:3px;\nbackground:none;\n}\n\nQColorDialog QPushButton,QFileDialog QPushButton{\nmin-width:80px;\n}\n\nQToolButton#qt_calendar_prevmonth{\nicon-size:0px;\nmin-width:20px;\nimage:url(:/qss/flatgray/calendar_prevmonth.png);\n}\n\nQToolButton#qt_calendar_nextmonth{\nicon-size:0px;\nmin-width:20px;\nimage:url(:/qss/flatgray/calendar_nextmonth.png);\n}\n\nQToolButton#qt_calendar_prevmonth,QToolButton#qt_calendar_nextmonth,QToolButton#qt_calendar_monthbutton,QToolButton#qt_calendar_yearbutton{\nborder:0px solid #57595B;\nborder-radius:3px;\nmargin:3px 3px 3px 3px;\npadding:3px;\nbackground:none;\n}\n\nQToolButton#qt_calendar_prevmonth:hover,QToolButton#qt_calendar_nextmonth:hover,QToolButton#qt_calendar_monthbutton:hover,QToolButton#qt_calendar_yearbutton:hover,QToolButton#qt_calendar_prevmonth:pressed,QToolButton#qt_calendar_nextmonth:pressed,QToolButton#qt_calendar_monthbutton:pressed,QToolButton#qt_calendar_yearbutton:pressed{\nborder:1px solid #B6B6B6;\n}\n\nQCalendarWidget QSpinBox#qt_calendar_yearedit{\nmargin:2px;\n}\n\nQCalendarWidget QToolButton::menu-indicator{\nimage:None;\n}\n\nQCalendarWidget QTableView{\nborder-width:0px;\n}\n\nQCalendarWidget QWidget#qt_calendar_navigationbar{\nborder:1px solid #B6B6B6;\nborder-width:1px 1px 0px 1px;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);\n}\n\nQTableView[model=\"true\"]::item{\npadding:0px;\nmargin:0px;\n}\n\nQTableView QLineEdit,QTableView QComboBox,QTableView QSpinBox,QTableView QDoubleSpinBox,QTableView QDateEdit,QTableView QTimeEdit,QTableView QDateTimeEdit{\nborder-width:0px;\nborder-radius:0px;\n}\n\nQTableView QLineEdit:focus,QTableView QComboBox:focus,QTableView QSpinBox:focus,QTableView QDoubleSpinBox:focus,QTableView QDateEdit:focus,QTableView QTimeEdit:focus,QTableView QDateTimeEdit:focus{\nborder-width:0px;\nborder-radius:0px;\n}\n\nQLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{\nbackground:#FFFFFF;\n}\n\nQTabWidget::pane:top{top:-1px;}\nQTabWidget::pane:bottom{bottom:-1px;}\nQTabWidget::pane:left{right:-1px;}\nQTabWidget::pane:right{left:-1px;}\n\nQDialog,QDial,#QUIWidgetMain{\nbackground-color:#FFFFFF;\ncolor:#57595B;\n}\n\nQDialogButtonBox>QPushButton{\nmin-width:50px;\n}\n\nQListView[noborder=\"true\"],QTreeView[noborder=\"true\"],QTabWidget[noborder=\"true\"]::pane{\nborder-width:0px;\n}\n\nQToolBar>*,QStatusBar>*{\nmargin:2px;\n}\n\n*:disabled,QMenu::item:disabled,QTabBar:tab:disabled,QHeaderView::section:disabled{\nbackground:#FFFFFF;\nborder-color:#E4E4E4;\ncolor:#B6B6B6;\n}\n\n/*TextColor:#57595B*/\n/*PanelColor:#FFFFFF*/\n/*BorderColor:#B6B6B6*/\n/*NormalColorStart:#E4E4E4*/\n/*NormalColorEnd:#E4E4E4*/\n/*DarkColorStart:#F6F6F6*/\n/*DarkColorEnd:#F6F6F6*/\n/*HighColor:#575959*/"
  },
  {
    "path": "res/theme/feiyangqingyun/qss/lightblue.css",
    "content": "QPalette{background:#EAF7FF;}*{outline:0px;color:#386487;}\n\nQGraphicsView{\nborder:1px solid #C0DCF2;\nqproperty-backgroundBrush:#EAF7FF;\n}\n\nQWidget[form=\"true\"],QLabel[frameShape=\"1\"],QGraphicsView{\nborder:1px solid #C0DCF2;\nborder-radius:0px;\n}\n\nQWidget[form=\"bottom\"]{\nbackground:#DEF0FE;\n}\n\nQWidget[form=\"bottom\"] .QFrame{\nborder:1px solid #386487;\n}\n\nQWidget[form=\"bottom\"] QLabel,QWidget[form=\"title\"] QLabel{\nborder-radius:0px;\ncolor:#386487;\nbackground:none;\nborder-style:none;\n}\n\nQWidget[form=\"title\"],QWidget[nav=\"left\"],QWidget[nav=\"top\"] QAbstractButton{\nborder-style:none;\nborder-radius:0px;\npadding:5px;\ncolor:#386487;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6);\n}\n\nQWidget[nav=\"top\"] QAbstractButton:hover,QWidget[nav=\"top\"] QAbstractButton:pressed,QWidget[nav=\"top\"] QAbstractButton:checked{\nborder-style:solid;\nborder-width:0px 0px 2px 0px;\npadding:4px 4px 2px 4px;\nborder-color:#386488;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F2F9FF,stop:1 #DAEFFF);\n}\n\nQWidget[nav=\"left\"] QAbstractButton{\nborder-radius:0px;\ncolor:#386487;\nbackground:none;\nborder-style:none;\n}\n\nQWidget[nav=\"left\"] QAbstractButton:hover{\ncolor:#FFFFFF;\nbackground-color:#386488;\n}\n\nQWidget[nav=\"left\"] QAbstractButton:checked,QWidget[nav=\"left\"] QAbstractButton:pressed{\ncolor:#386487;\nborder-style:solid;\nborder-width:0px 0px 0px 2px;\npadding:4px 4px 4px 2px;\nborder-color:#386488;\nbackground-color:#EAF7FF;\n}\n\nQWidget[video=\"true\"] QLabel{\ncolor:#386487;\nborder:1px solid #C0DCF2;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6);\n}\n\nQWidget[video=\"true\"] QLabel:focus{\nborder:1px solid #386488;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F2F9FF,stop:1 #DAEFFF);\n}\n\nQLineEdit:read-only{\nbackground-color:#DEF0FE;\n}\n\nQLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{\nborder:1px solid #C0DCF2;\nborder-radius:3px;\npadding:2px;\nbackground:none;\nselection-background-color:#386488;\nselection-color:#FFFFFF;\n}\n\nQLineEdit:focus,QTextEdit:focus,QPlainTextEdit:focus,QSpinBox:focus,QDoubleSpinBox:focus,QComboBox:focus,QDateEdit:focus,QTimeEdit:focus,QDateTimeEdit:focus,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QSpinBox:hover,QDoubleSpinBox:hover,QComboBox:hover,QDateEdit:hover,QTimeEdit:hover,QDateTimeEdit:hover{\nborder:1px solid #C0DCF2;\n}\n\nQLineEdit[echoMode=\"2\"]{\nlineedit-password-character:9679;\n}\n\n.QFrame{\nborder:1px solid #C0DCF2;\nborder-radius:3px;\n}\n\n.QGroupBox{\nborder:1px solid #C0DCF2;\nborder-radius:5px;\nmargin-top:3ex;\n}\n\n.QGroupBox::title{\nsubcontrol-origin:margin;\nposition:relative;\nleft:10px;\n}\n\n.QPushButton,.QToolButton{\nborder-style:none;\nborder:1px solid #C0DCF2;\ncolor:#386487;\npadding:5px;\nmin-height:15px;\nborder-radius:5px;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6);\n}\n\n.QPushButton:hover,.QToolButton:hover{\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F2F9FF,stop:1 #DAEFFF);\n}\n\n.QPushButton:pressed,.QToolButton:pressed{\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6);\n}\n\n.QToolButton::menu-indicator{\nimage:None;\n}\n\nQToolButton#btnMenu,QPushButton#btnMenu_Min,QPushButton#btnMenu_Max,QPushButton#btnMenu_Close{\nborder-radius:3px;\ncolor:#386487;\npadding:3px;\nmargin:0px;\nbackground:none;\nborder-style:none;\n}\n\nQToolButton#btnMenu:hover,QPushButton#btnMenu_Min:hover,QPushButton#btnMenu_Max:hover{\ncolor:#FFFFFF;\nmargin:1px 1px 2px 1px;\nbackground-color:rgba(51,127,209,230);\n}\n\nQPushButton#btnMenu_Close:hover{\ncolor:#FFFFFF;\nmargin:1px 1px 2px 1px;\nbackground-color:rgba(238,0,0,128);\n}\n\nQRadioButton::indicator{\nwidth:15px;\nheight:15px;\n}\n\nQRadioButton::indicator::unchecked{\nimage:url(:/qss/lightblue/radiobutton_unchecked.png);\n}\n\nQRadioButton::indicator::unchecked:disabled{\nimage:url(:/qss/lightblue/radiobutton_unchecked_disable.png);\n}\n\nQRadioButton::indicator::checked{\nimage:url(:/qss/lightblue/radiobutton_checked.png);\n}\n\nQRadioButton::indicator::checked:disabled{\nimage:url(:/qss/lightblue/radiobutton_checked_disable.png);\n}\n\nQGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{\npadding:0px 0px 0px 0px;\n}\n\nQCheckBox::indicator,QGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{\nwidth:13px;\nheight:13px;\n}\n\nQCheckBox::indicator:unchecked,QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QListView::indicator:unchecked,QTableView::indicator:unchecked{\nimage:url(:/qss/lightblue/checkbox_unchecked.png);\n}\n\nQCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QListView::indicator:unchecked:disabled,QTableView::indicator:unchecked:disabled{\nimage:url(:/qss/lightblue/checkbox_unchecked_disable.png);\n}\n\nQCheckBox::indicator:checked,QGroupBox::indicator:checked,QTreeView::indicator:checked,QListView::indicator:checked,QTableView::indicator:checked{\nimage:url(:/qss/lightblue/checkbox_checked.png);\n}\n\nQCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,QTreeView::indicator:checked:disabled,QListView::indicator:checked:disabled,QTableView::indicator:checked:disabled{\nimage:url(:/qss/lightblue/checkbox_checked_disable.png);\n}\n\nQCheckBox::indicator:indeterminate,QGroupBox::indicator:indeterminate,QTreeView::indicator:indeterminate,QListView::indicator:indeterminate,QTableView::indicator:indeterminate{\nimage:url(:/qss/lightblue/checkbox_parcial.png);\n}\n\nQCheckBox::indicator:indeterminate:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:indeterminate:disabled,QListView::indicator:indeterminate:disabled,QTableView::indicator:indeterminate:disabled{\nimage:url(:/qss/lightblue/checkbox_parcial_disable.png);\n}\n\nQTimeEdit::up-button,QDateEdit::up-button,QDateTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button{\nimage:url(:/qss/lightblue/add_top.png);\nwidth:10px;\nheight:10px;\npadding:2px 5px 0px 0px;\n}\n\nQTimeEdit::down-button,QDateEdit::down-button,QDateTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button{\nimage:url(:/qss/lightblue/add_bottom.png);\nwidth:10px;\nheight:10px;\npadding:0px 5px 2px 0px;\n}\n\nQTimeEdit::up-button:pressed,QDateEdit::up-button:pressed,QDateTimeEdit::up-button:pressed,QDoubleSpinBox::up-button:pressed,QSpinBox::up-button:pressed{\ntop:-2px;\n}\n  \nQTimeEdit::down-button:pressed,QDateEdit::down-button:pressed,QDateTimeEdit::down-button:pressed,QDoubleSpinBox::down-button:pressed,QSpinBox::down-button:pressed,QSpinBox::down-button:pressed{\nbottom:-2px;\n}\n\nQComboBox::down-arrow,QDateEdit[calendarPopup=\"true\"]::down-arrow,QTimeEdit[calendarPopup=\"true\"]::down-arrow,QDateTimeEdit[calendarPopup=\"true\"]::down-arrow{\nimage:url(:/qss/lightblue/add_bottom.png);\nwidth:10px;\nheight:10px;\nright:2px;\n}\n\nQComboBox::drop-down,QDateEdit::drop-down,QTimeEdit::drop-down,QDateTimeEdit::drop-down{\nsubcontrol-origin:padding;\nsubcontrol-position:top right;\nwidth:15px;\nborder-left-width:0px;\nborder-left-style:solid;\nborder-top-right-radius:3px;\nborder-bottom-right-radius:3px;\nborder-left-color:#C0DCF2;\n}\n\nQComboBox::drop-down:on{\ntop:1px;\n}\n\nQMenuBar::item{\ncolor:#386487;\nbackground-color:#DEF0FE;\nmargin:0px;\npadding:3px 10px;\n}\n\nQMenu,QMenuBar,QMenu:disabled,QMenuBar:disabled{\ncolor:#386487;\nbackground-color:#DEF0FE;\nborder:1px solid #C0DCF2;\nmargin:0px;\n}\n\nQMenu::item{\npadding:3px 20px;\n}\n\nQMenu::indicator{\nwidth:20px;\nheight:13px;\n}\n\nQMenu::indicator::checked{\nimage:url(:/qss/lightblue/menu_checked.png);\n}\n\nQMenu::right-arrow{\nimage:url(:/qss/lightblue/arrow_right.png);\nwidth:13px;\nheight:13px;\npadding:0px 3px 0px 0px;\n}\n\nQMenu::item:selected,QMenuBar::item:selected{\ncolor:#386487;\nborder:0px solid #C0DCF2;\nbackground:#F2F9FF;\n}\n\nQMenu::separator{\nheight:1px;\nbackground:#C0DCF2;\n}\n\nQProgressBar{\nmin-height:10px;\nbackground:#DEF0FE;\nborder-radius:5px;\ntext-align:center;\nborder:1px solid #DEF0FE;\n}\n\nQProgressBar:chunk{\nborder-radius:5px;\nbackground-color:#C0DCF2;\n}\n\nQSlider::groove:horizontal{\nheight:8px;\nborder-radius:4px;\nbackground:#DEF0FE;\n}\n\nQSlider::add-page:horizontal{\nheight:8px;\nborder-radius:4px;\nbackground:#DEF0FE;\n}\n\nQSlider::sub-page:horizontal{\nheight:8px;\nborder-radius:4px;\nbackground:#C0DCF2;\n}\n\nQSlider::handle:horizontal{\nwidth:13px;\nmargin-top:-3px;\nmargin-bottom:-3px;\nborder-radius:6px;\nbackground:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #EAF7FF,stop:0.8 #C0DCF2);\n}\n\nQSlider::groove:vertical{\nwidth:8px;\nborder-radius:4px;\nbackground:#DEF0FE;\n}\n\nQSlider::add-page:vertical{\nwidth:8px;\nborder-radius:4px;\nbackground:#C0DCF2;\n}\n\nQSlider::sub-page:vertical{\nwidth:8px;\nborder-radius:4px;\nbackground:#DEF0FE;\n}\n\nQSlider::handle:vertical{\nheight:14px;\nmargin-left:-3px;\nmargin-right:-3px;\nborder-radius:6px;\nbackground:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #EAF7FF,stop:0.8 #C0DCF2);\n}\n\nQScrollBar:horizontal{\nbackground:#DEF0FE;\npadding:0px;\nborder-radius:6px;\nmax-height:12px;\n}\n\nQScrollBar::handle:horizontal{\nbackground:#C0DCF2;\nmin-width:50px;\nborder-radius:6px;\n}\n\nQScrollBar::handle:horizontal:hover{\nbackground:#386488;\n}\n\nQScrollBar::handle:horizontal:pressed{\nbackground:#386488;\n}\n\nQScrollBar::add-page:horizontal{\nbackground:none;\n}\n\nQScrollBar::sub-page:horizontal{\nbackground:none;\n}\n\nQScrollBar::add-line:horizontal{\nbackground:none;\n}\n\nQScrollBar::sub-line:horizontal{\nbackground:none;\n}\n\nQScrollBar:vertical{\nbackground:#DEF0FE;\npadding:0px;\nborder-radius:6px;\nmax-width:12px;\n}\n\nQScrollBar::handle:vertical{\nbackground:#C0DCF2;\nmin-height:50px;\nborder-radius:6px;\n}\n\nQScrollBar::handle:vertical:hover{\nbackground:#386488;\n}\n\nQScrollBar::handle:vertical:pressed{\nbackground:#386488;\n}\n\nQScrollBar::add-page:vertical{\nbackground:none;\n}\n\nQScrollBar::sub-page:vertical{\nbackground:none;\n}\n\nQScrollBar::add-line:vertical{\nbackground:none;\n}\n\nQScrollBar::sub-line:vertical{\nbackground:none;\n}\n\nQScrollArea{\nborder:0px;\n}\n\nQTreeView,QListView,QTableView,QTabWidget::pane{\nborder:1px solid #C0DCF2;\nselection-background-color:#F2F9FF;\nselection-color:#386487;\nalternate-background-color:#DAEFFF;\ngridline-color:#C0DCF2;\n}\n\nQTreeView::branch:closed:has-children{\nmargin:4px;\nborder-image:url(:/qss/lightblue/branch_open.png);\n}\n\nQTreeView::branch:open:has-children{\nmargin:4px;\nborder-image:url(:/qss/lightblue/branch_close.png);\n}\n\nQTreeView,QListView,QTableView,QSplitter::handle,QTreeView::branch{\nbackground:#EAF7FF;\n}\n\nQTableView::item:selected,QListView::item:selected,QTreeView::item:selected{\ncolor:#386487;\nbackground:#C0DEF6;\n}\n\nQTableView::item:hover,QListView::item:hover,QTreeView::item:hover,QHeaderView,QHeaderView::section,QTableCornerButton:section{\ncolor:#386487;\nbackground:#DAEFFF;\n}\n\nQTableView::item,QListView::item,QTreeView::item{\npadding:1px;\nmargin:0px;\nborder:0px;\n}\n\nQHeaderView::section,QTableCornerButton:section{\npadding:3px;\nmargin:0px;\nborder:1px solid #C0DCF2;\nborder-left-width:0px;\nborder-right-width:1px;\nborder-top-width:0px;\nborder-bottom-width:1px;\n}\n\nQTabBar::tab{\nborder:1px solid #C0DCF2;\ncolor:#386487;\nmargin:0px;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F2F9FF,stop:1 #DAEFFF);\n}\n\nQTabBar::tab:selected{\nborder-style:solid;\nborder-color:#386488;\nbackground:#EAF7FF;\n}\n\nQTabBar::tab:top,QTabBar::tab:bottom{\npadding:3px 8px 3px 8px;\n}\n\nQTabBar::tab:left,QTabBar::tab:right{\npadding:8px 3px 8px 3px;\n}\n\nQTabBar::tab:top:selected{\nborder-width:2px 0px 0px 0px;\n}\n\nQTabBar::tab:right:selected{\nborder-width:0px 0px 0px 2px;\n}\n\nQTabBar::tab:bottom:selected{\nborder-width:0px 0px 2px 0px;\n}\n\nQTabBar::tab:left:selected{\nborder-width:0px 2px 0px 0px;\n}\n\nQTabBar::tab:first:top:selected,QTabBar::tab:first:bottom:selected{\nborder-left-width:1px;\nborder-left-color:#C0DCF2;\n}\n\nQTabBar::tab:first:left:selected,QTabBar::tab:first:right:selected{\nborder-top-width:1px;\nborder-top-color:#C0DCF2;\n}\n\nQTabBar::tab:last:top:selected,QTabBar::tab:last:bottom:selected{\nborder-right-width:1px;\nborder-right-color:#C0DCF2;\n}\n\nQTabBar::tab:last:left:selected,QTabBar::tab:last:right:selected{\nborder-bottom-width:1px;\nborder-bottom-color:#C0DCF2;\n}\n\nQStatusBar::item{\nborder:0px solid #DEF0FE;\nborder-radius:3px;\n}\n\nQToolBox::tab,QGroupBox#gboxDevicePanel,QGroupBox#gboxDeviceTitle,QFrame#gboxDevicePanel,QFrame#gboxDeviceTitle{\npadding:3px;\nborder-radius:5px;\ncolor:#386487;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6);\n}\n\nQToolTip{\nborder:0px solid #386487;\npadding:1px;\ncolor:#386487;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6);\n}\n\nQToolBox::tab:selected{\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F2F9FF,stop:1 #DAEFFF);\n}\n\nQPrintPreviewDialog QToolButton{\nborder:0px solid #386487;\nborder-radius:0px;\nmargin:0px;\npadding:3px;\nbackground:none;\n}\n\nQColorDialog QPushButton,QFileDialog QPushButton{\nmin-width:80px;\n}\n\nQToolButton#qt_calendar_prevmonth{\nicon-size:0px;\nmin-width:20px;\nimage:url(:/qss/lightblue/calendar_prevmonth.png);\n}\n\nQToolButton#qt_calendar_nextmonth{\nicon-size:0px;\nmin-width:20px;\nimage:url(:/qss/lightblue/calendar_nextmonth.png);\n}\n\nQToolButton#qt_calendar_prevmonth,QToolButton#qt_calendar_nextmonth,QToolButton#qt_calendar_monthbutton,QToolButton#qt_calendar_yearbutton{\nborder:0px solid #386487;\nborder-radius:3px;\nmargin:3px 3px 3px 3px;\npadding:3px;\nbackground:none;\n}\n\nQToolButton#qt_calendar_prevmonth:hover,QToolButton#qt_calendar_nextmonth:hover,QToolButton#qt_calendar_monthbutton:hover,QToolButton#qt_calendar_yearbutton:hover,QToolButton#qt_calendar_prevmonth:pressed,QToolButton#qt_calendar_nextmonth:pressed,QToolButton#qt_calendar_monthbutton:pressed,QToolButton#qt_calendar_yearbutton:pressed{\nborder:1px solid #C0DCF2;\n}\n\nQCalendarWidget QSpinBox#qt_calendar_yearedit{\nmargin:2px;\n}\n\nQCalendarWidget QToolButton::menu-indicator{\nimage:None;\n}\n\nQCalendarWidget QTableView{\nborder-width:0px;\n}\n\nQCalendarWidget QWidget#qt_calendar_navigationbar{\nborder:1px solid #C0DCF2;\nborder-width:1px 1px 0px 1px;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6);\n}\n\nQTableView[model=\"true\"]::item{\npadding:0px;\nmargin:0px;\n}\n\nQTableView QLineEdit,QTableView QComboBox,QTableView QSpinBox,QTableView QDoubleSpinBox,QTableView QDateEdit,QTableView QTimeEdit,QTableView QDateTimeEdit{\nborder-width:0px;\nborder-radius:0px;\n}\n\nQTableView QLineEdit:focus,QTableView QComboBox:focus,QTableView QSpinBox:focus,QTableView QDoubleSpinBox:focus,QTableView QDateEdit:focus,QTableView QTimeEdit:focus,QTableView QDateTimeEdit:focus{\nborder-width:0px;\nborder-radius:0px;\n}\n\nQLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{\nbackground:#EAF7FF;\n}\n\nQTabWidget::pane:top{top:-1px;}\nQTabWidget::pane:bottom{bottom:-1px;}\nQTabWidget::pane:left{right:-1px;}\nQTabWidget::pane:right{left:-1px;}\n\nQDialog,QDial,#QUIWidgetMain{\nbackground-color:#EAF7FF;\ncolor:#386487;\n}\n\nQDialogButtonBox>QPushButton{\nmin-width:50px;\n}\n\nQListView[noborder=\"true\"],QTreeView[noborder=\"true\"],QTabWidget[noborder=\"true\"]::pane{\nborder-width:0px;\n}\n\nQToolBar>*,QStatusBar>*{\nmargin:2px;\n}\n\n*:disabled,QMenu::item:disabled,QTabBar:tab:disabled,QHeaderView::section:disabled{\nbackground:#EAF7FF;\nborder-color:#DEF0FE;\ncolor:#C0DCF2;\n}\n\n/*TextColor:#386487*/\n/*PanelColor:#EAF7FF*/\n/*BorderColor:#C0DCF2*/\n/*NormalColorStart:#DEF0FE*/\n/*NormalColorEnd:#C0DEF6*/\n/*DarkColorStart:#F2F9FF*/\n/*DarkColorEnd:#DAEFFF*/\n/*HighColor:#386488*/"
  },
  {
    "path": "res/theme/feiyangqingyun/qss.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/themes/feiyangqingyun\">\n        <file>qss/flatgray.css</file>\n        <file>qss/lightblue.css</file>\n        <file>qss/flatgray/add_bottom.png</file>\n        <file>qss/flatgray/add_left.png</file>\n        <file>qss/flatgray/add_right.png</file>\n        <file>qss/flatgray/add_top.png</file>\n        <file>qss/flatgray/arrow_bottom.png</file>\n        <file>qss/flatgray/arrow_left.png</file>\n        <file>qss/flatgray/arrow_right.png</file>\n        <file>qss/flatgray/arrow_top.png</file>\n        <file>qss/flatgray/branch_close.png</file>\n        <file>qss/flatgray/branch_open.png</file>\n        <file>qss/flatgray/calendar_nextmonth.png</file>\n        <file>qss/flatgray/calendar_prevmonth.png</file>\n        <file>qss/flatgray/checkbox_checked.png</file>\n        <file>qss/flatgray/checkbox_checked_disable.png</file>\n        <file>qss/flatgray/checkbox_parcial.png</file>\n        <file>qss/flatgray/checkbox_parcial_disable.png</file>\n        <file>qss/flatgray/checkbox_unchecked.png</file>\n        <file>qss/flatgray/checkbox_unchecked_disable.png</file>\n        <file>qss/flatgray/menu_checked.png</file>\n        <file>qss/flatgray/radiobutton_checked.png</file>\n        <file>qss/flatgray/radiobutton_checked_disable.png</file>\n        <file>qss/flatgray/radiobutton_unchecked.png</file>\n        <file>qss/flatgray/radiobutton_unchecked_disable.png</file>\n        <file>qss/lightblue/add_bottom.png</file>\n        <file>qss/lightblue/add_left.png</file>\n        <file>qss/lightblue/add_right.png</file>\n        <file>qss/lightblue/add_top.png</file>\n        <file>qss/lightblue/arrow_bottom.png</file>\n        <file>qss/lightblue/arrow_left.png</file>\n        <file>qss/lightblue/arrow_right.png</file>\n        <file>qss/lightblue/arrow_top.png</file>\n        <file>qss/lightblue/branch_close.png</file>\n        <file>qss/lightblue/branch_open.png</file>\n        <file>qss/lightblue/calendar_nextmonth.png</file>\n        <file>qss/lightblue/calendar_prevmonth.png</file>\n        <file>qss/lightblue/checkbox_checked.png</file>\n        <file>qss/lightblue/checkbox_checked_disable.png</file>\n        <file>qss/lightblue/checkbox_parcial.png</file>\n        <file>qss/lightblue/checkbox_parcial_disable.png</file>\n        <file>qss/lightblue/checkbox_unchecked.png</file>\n        <file>qss/lightblue/checkbox_unchecked_disable.png</file>\n        <file>qss/lightblue/menu_checked.png</file>\n        <file>qss/lightblue/radiobutton_checked.png</file>\n        <file>qss/lightblue/radiobutton_checked_disable.png</file>\n        <file>qss/lightblue/radiobutton_unchecked.png</file>\n        <file>qss/lightblue/radiobutton_unchecked_disable.png</file>\n        <file>qss/blacksoft.css</file>\n        <file>qss/blacksoft/add_bottom.png</file>\n        <file>qss/blacksoft/add_left.png</file>\n        <file>qss/blacksoft/add_right.png</file>\n        <file>qss/blacksoft/add_top.png</file>\n        <file>qss/blacksoft/arrow_bottom.png</file>\n        <file>qss/blacksoft/arrow_left.png</file>\n        <file>qss/blacksoft/arrow_right.png</file>\n        <file>qss/blacksoft/arrow_top.png</file>\n        <file>qss/blacksoft/branch_close.png</file>\n        <file>qss/blacksoft/branch_open.png</file>\n        <file>qss/blacksoft/calendar_nextmonth.png</file>\n        <file>qss/blacksoft/calendar_prevmonth.png</file>\n        <file>qss/blacksoft/checkbox_checked.png</file>\n        <file>qss/blacksoft/checkbox_checked_disable.png</file>\n        <file>qss/blacksoft/checkbox_parcial.png</file>\n        <file>qss/blacksoft/checkbox_parcial_disable.png</file>\n        <file>qss/blacksoft/checkbox_unchecked.png</file>\n        <file>qss/blacksoft/checkbox_unchecked_disable.png</file>\n        <file>qss/blacksoft/menu_checked.png</file>\n        <file>qss/blacksoft/radiobutton_checked.png</file>\n        <file>qss/blacksoft/radiobutton_checked_disable.png</file>\n        <file>qss/blacksoft/radiobutton_unchecked.png</file>\n        <file>qss/blacksoft/radiobutton_unchecked_disable.png</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "res/vpn/sing-box-vpn.json",
    "content": "{\n    \"log\": {\n        \"level\": \"info\"\n    },\n    \"dns\": {\n        \"fakeip\": {\n            \"enabled\": true,\n            \"inet4_range\": \"198.18.0.0/15\",\n            \"inet6_range\": \"fc00::/18\"\n        },\n        \"servers\": [\n            {\n                \"tag\": \"dns-remote\",\n                \"address\": \"8.8.8.8\",\n                \"detour\": \"neko-socks\"\n            },\n            {\n                \"tag\": \"dns-direct\",\n                \"address\": \"%DNS_ADDRESS%\",\n                \"detour\": \"direct\"\n            },\n            {\n                \"address\": \"fakeip\",\n                \"tag\": \"dns-fake\"\n            },\n            {\n                \"address\": \"rcode://success\",\n                \"tag\": \"dns-block\"\n            }\n        ],\n        \"rules\": [\n            {\n                \"query_type\": [\n                    32,\n                    33\n                ],\n                \"server\": \"dns-block\"\n            },\n            {\n                \"domain_suffix\": [\n                    \".lan\"\n                ],\n                \"server\": \"dns-block\"\n            },\n            {\n                \"process_name\": [\n                    \"nekoray_core\",\n                    \"nekoray_core.exe\",\n                    \"nekobox_core\",\n                    \"nekobox_core.exe\"\n                ],\n                \"server\": \"dns-direct\"\n            },\n            {\n                \"inbound\": \"%FAKE_DNS_INBOUND%\",\n                \"server\": \"dns-fake\"\n            }\n        ]\n    },\n    \"inbounds\": [\n        {\n            \"type\": \"tun\",\n            \"tag\": \"tun-in\",\n            \"interface_name\": \"%TUN_NAME%\",\n            \"inet4_address\": \"172.19.0.1/28\",\n            //%IPV6_ADDRESS%\n            \"mtu\": %MTU%,\n            \"auto_route\": true,\n            \"strict_route\": %STRICT_ROUTE%,\n            \"stack\": \"%STACK%\",\n            \"endpoint_independent_nat\": true,\n            \"sniff\": false\n        }\n    ],\n    \"outbounds\": [\n        {\n            \"type\": \"socks\",\n            \"tag\": \"neko-socks\",\n            \"udp_fragment\": true,\n            //%SOCKS_USER_PASS%\n            \"server\": \"127.0.0.1\",\n            \"server_port\": %PORT%\n        },\n        {\n            \"type\": \"block\",\n            \"tag\": \"block\"\n        },\n        {\n            \"type\": \"direct\",\n            \"tag\": \"direct\"\n        },\n        {\n            \"type\": \"dns\",\n            \"tag\": \"dns-out\"\n        }\n    ],\n    \"route\": {\n        \"final\": \"%FINAL_OUT%\",\n        \"auto_detect_interface\": true,\n        \"rules\": [\n            {\n                \"network\": \"udp\",\n                \"port\": [\n                    135,\n                    137,\n                    138,\n                    139,\n                    5353\n                ],\n                \"outbound\": \"block\"\n            },\n            {\n                \"ip_cidr\": [\n                    \"224.0.0.0/3\",\n                    \"ff00::/8\"\n                ],\n                \"outbound\": \"block\"\n            },\n            {\n                \"source_ip_cidr\": [\n                    \"224.0.0.0/3\",\n                    \"ff00::/8\"\n                ],\n                \"outbound\": \"block\"\n            },\n            {\n                \"port\": 53,\n                \"process_name\": [\n                    \"nekoray_core\",\n                    \"nekoray_core.exe\"\n                ],\n                \"outbound\": \"dns-out\"\n            },\n            {\n                \"process_name\": [\n                    \"nekoray_core\",\n                    \"nekoray_core.exe\",\n                    \"nekobox_core\",\n                    \"nekobox_core.exe\"\n                ],\n                \"outbound\": \"direct\"\n            }\n            //%PROCESS_NAME_RULE%\n            //%CIDR_RULE%\n            ,\n            {\n                \"port\": 53,\n                \"outbound\": \"dns-out\"\n            }\n        ]\n    }\n}"
  },
  {
    "path": "res/vpn/vpn-run-root.sh",
    "content": "#!/bin/sh\nset -e\nset -x\n\nif [ \"$EUID\" -ne 0 ]; then\n  echo \"[Warning] Tun script not running as root\"\nfi\n\ncommand -v pkill >/dev/null 2>&1 || echo \"[Warning] pkill not found\"\n\nBASEDIR=$(dirname \"$0\")\ncd $BASEDIR\n\npre_start_linux() {\n  # for Tun2Socket\n  iptables -I INPUT -s 172.19.0.2 -d 172.19.0.1 -p tcp -j ACCEPT\n  ip6tables -I INPUT -s fdfe:dcba:9876::2 -d fdfe:dcba:9876::1 -p tcp -j ACCEPT\n}\n\nstart() {\n  pre_start_linux\n  \"./nekobox_core\" run -c \"$CONFIG_PATH\"\n}\n\nstop() {\n  iptables -D INPUT -s 172.19.0.2 -d 172.19.0.1 -p tcp -j ACCEPT\n  ip6tables -D INPUT -s fdfe:dcba:9876::2 -d fdfe:dcba:9876::1 -p tcp -j ACCEPT\n}\n\nif [ \"$1\" != \"stop\" ]; then\n  start || true\nfi\n\nstop || true\n"
  },
  {
    "path": "rpc/gRPC.cpp",
    "content": "#include \"gRPC.h\"\n\n#include <utility>\n#include <QStringList>\n\n#ifndef NKR_NO_GRPC\n\n#include \"main/NekoGui.hpp\"\n\n#include <QCoreApplication>\n#include <QNetworkAccessManager>\n#include <QNetworkReply>\n#include <QTimer>\n#include <QtEndian>\n#include <QThread>\n#include <QMutex>\n#include <QAbstractNetworkCache>\n\nnamespace QtGrpc {\n    const char *GrpcAcceptEncodingHeader = \"grpc-accept-encoding\";\n    const char *AcceptEncodingHeader = \"accept-encoding\";\n    const char *TEHeader = \"te\";\n    const char *GrpcStatusHeader = \"grpc-status\";\n    const char *GrpcStatusMessage = \"grpc-message\";\n    const int GrpcMessageSizeHeaderSize = 5;\n\n    class NoCache : public QAbstractNetworkCache {\n    public:\n        QNetworkCacheMetaData metaData(const QUrl &url) override {\n            return {};\n        }\n        void updateMetaData(const QNetworkCacheMetaData &metaData) override {\n        }\n        QIODevice *data(const QUrl &url) override {\n            return nullptr;\n        }\n        bool remove(const QUrl &url) override {\n            return false;\n        }\n        [[nodiscard]] qint64 cacheSize() const override {\n            return 0;\n        }\n        QIODevice *prepare(const QNetworkCacheMetaData &metaData) override {\n            return nullptr;\n        }\n        void insert(QIODevice *device) override {\n        }\n        void clear() override {\n        }\n    };\n\n    class Http2GrpcChannelPrivate {\n    private:\n        QThread *thread;\n        QNetworkAccessManager *nm;\n\n        QString url_base;\n        QString serviceName;\n        QByteArray nekoray_auth;\n\n        // async\n        QNetworkReply *post(const QString &method, const QString &service, const QByteArray &args) {\n            QUrl callUrl = url_base + \"/\" + service + \"/\" + method;\n            // qDebug() << \"Service call url: \" << callUrl;\n\n            QNetworkRequest request(callUrl);\n            // request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);\n            // request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);\n#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)\n            request.setAttribute(QNetworkRequest::Http2DirectAttribute, true);\n#endif\n            request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String{\"application/grpc\"});\n            request.setRawHeader(\"Cache-Control\", \"no-store\");\n            request.setRawHeader(GrpcAcceptEncodingHeader, QByteArray{\"identity,deflate,gzip\"});\n            request.setRawHeader(AcceptEncodingHeader, QByteArray{\"identity,gzip\"});\n            request.setRawHeader(TEHeader, QByteArray{\"trailers\"});\n            request.setRawHeader(\"nekoray_auth\", nekoray_auth);\n\n            QByteArray msg(GrpcMessageSizeHeaderSize, '\\0');\n            *reinterpret_cast<int *>(msg.data() + 1) = qToBigEndian((int) args.size());\n            msg += args;\n            // qDebug() << \"SEND: \" << msg.size();\n\n            QNetworkReply *networkReply = nm->post(request, msg);\n            return networkReply;\n        }\n\n        static QByteArray processReply(QNetworkReply *networkReply, QNetworkReply::NetworkError &statusCode) {\n            // Check if no network error occured\n            if (networkReply->error() != QNetworkReply::NoError) {\n                statusCode = networkReply->error();\n                return {};\n            }\n\n            // Check if server answer with error\n            auto errCode = networkReply->rawHeader(GrpcStatusHeader).toInt();\n            if (errCode != 0) {\n                QStringList errstr;\n                errstr << \"grpc-status error code:\" << Int2String(errCode) << \", error msg:\"\n                       << QLatin1String(networkReply->rawHeader(GrpcStatusMessage));\n                MW_show_log(errstr.join(\" \"));\n                statusCode = QNetworkReply::NetworkError::ProtocolUnknownError;\n                return {};\n            }\n            statusCode = QNetworkReply::NetworkError::NoError;\n            return networkReply->readAll().mid(GrpcMessageSizeHeaderSize);\n        }\n\n        QNetworkReply::NetworkError call(const QString &method, const QString &service, const QByteArray &args, QByteArray &qByteArray, int timeout_ms) {\n            QNetworkReply *networkReply = post(method, service, args);\n\n            QTimer *abortTimer = nullptr;\n            if (timeout_ms > 0) {\n                abortTimer = new QTimer;\n                abortTimer->setSingleShot(true);\n                abortTimer->setInterval(timeout_ms);\n                QObject::connect(abortTimer, &QTimer::timeout, networkReply, &QNetworkReply::abort);\n                abortTimer->start();\n            }\n\n            {\n                QEventLoop loop;\n                QObject::connect(networkReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);\n                loop.exec();\n            }\n\n            if (abortTimer != nullptr) {\n                abortTimer->stop();\n                abortTimer->deleteLater();\n            }\n\n            auto grpcStatus = QNetworkReply::NetworkError::ProtocolUnknownError;\n            qByteArray = processReply(networkReply, grpcStatus);\n            // qDebug() << __func__ << \"RECV: \" << qByteArray.toHex() << \"grpcStatus\" << grpcStatus;\n            // qDebug() << networkReply->rawHeaderPairs();\n\n            networkReply->deleteLater();\n            return grpcStatus;\n        }\n\n    public:\n        Http2GrpcChannelPrivate(const QString &url_, const QString &nekoray_auth_, const QString &serviceName_) {\n            url_base = \"http://\" + url_;\n            nekoray_auth = nekoray_auth_.toLatin1();\n            serviceName = serviceName_;\n            //\n            thread = new QThread;\n            nm = new QNetworkAccessManager();\n            nm->setCache(new NoCache);\n            nm->moveToThread(thread);\n            thread->start();\n        }\n\n        ~Http2GrpcChannelPrivate() {\n            nm->deleteLater();\n            thread->quit();\n            thread->wait();\n            thread->deleteLater();\n        }\n\n        QNetworkReply::NetworkError Call(const QString &methodName,\n                                         const google::protobuf::Message &req, google::protobuf::Message *rsp,\n                                         int timeout_ms = 0) {\n            if (!NekoGui::dataStore->core_running) return QNetworkReply::NetworkError(-1919);\n\n            std::string reqStr;\n            req.SerializeToString(&reqStr);\n            auto requestArray = QByteArray::fromStdString(reqStr);\n\n            QByteArray responseArray;\n            QNetworkReply::NetworkError err;\n            QMutex lock;\n            lock.lock();\n\n            runOnUiThread(\n                [&] {\n                    err = call(methodName, serviceName, requestArray, responseArray, timeout_ms);\n                    lock.unlock();\n                },\n                nm);\n\n            lock.lock();\n            lock.unlock();\n            // qDebug() << \"rsp err\" << err;\n            // qDebug() << \"rsp array\" << responseArray;\n\n            if (err != QNetworkReply::NetworkError::NoError) {\n                return err;\n            }\n            if (!rsp->ParseFromArray(responseArray.data(), responseArray.size())) {\n                return QNetworkReply::NetworkError(-114514);\n            }\n            return QNetworkReply::NetworkError::NoError;\n        }\n    };\n} // namespace QtGrpc\n\nnamespace NekoGui_rpc {\n\n    Client::Client(std::function<void(const QString &)> onError, const QString &target, const QString &token) {\n        this->make_grpc_channel = [=]() { return std::make_unique<QtGrpc::Http2GrpcChannelPrivate>(target, token, \"libcore.LibcoreService\"); };\n        this->default_grpc_channel = make_grpc_channel();\n        this->onError = std::move(onError);\n    }\n\n#define NOT_OK      \\\n    *rpcOK = false; \\\n    onError(QStringLiteral(\"QNetworkReply::NetworkError code: %1\\n\").arg(status));\n\n    void Client::Exit() {\n        libcore::EmptyReq request;\n        libcore::EmptyResp reply;\n        default_grpc_channel->Call(\"Exit\", request, &reply, 500);\n    }\n\n    QString Client::Start(bool *rpcOK, const libcore::LoadConfigReq &request) {\n        libcore::ErrorResp reply;\n        auto status = default_grpc_channel->Call(\"Start\", request, &reply);\n\n        if (status == QNetworkReply::NoError) {\n            *rpcOK = true;\n            return {reply.error().c_str()};\n        } else {\n            NOT_OK\n            return \"\";\n        }\n    }\n\n    QString Client::Stop(bool *rpcOK) {\n        libcore::EmptyReq request;\n        libcore::ErrorResp reply;\n        auto status = default_grpc_channel->Call(\"Stop\", request, &reply);\n\n        if (status == QNetworkReply::NoError) {\n            *rpcOK = true;\n            return {reply.error().c_str()};\n        } else {\n            NOT_OK\n            return \"\";\n        }\n    }\n\n    long long Client::QueryStats(const std::string &tag, const std::string &direct) {\n        libcore::QueryStatsReq request;\n        request.set_tag(tag);\n        request.set_direct(direct);\n\n        libcore::QueryStatsResp reply;\n        auto status = default_grpc_channel->Call(\"QueryStats\", request, &reply, 500);\n\n        if (status == QNetworkReply::NoError) {\n            return reply.traffic();\n        } else {\n            return 0;\n        }\n    }\n\n    std::string Client::ListConnections() {\n        libcore::EmptyReq request;\n        libcore::ListConnectionsResp reply;\n        auto status = default_grpc_channel->Call(\"ListConnections\", request, &reply, 500);\n\n        if (status == QNetworkReply::NoError) {\n            return reply.nekoray_connections_json();\n        } else {\n            return \"\";\n        }\n    }\n\n    //\n\n    libcore::TestResp Client::Test(bool *rpcOK, const libcore::TestReq &request) {\n        libcore::TestResp reply;\n        auto status = make_grpc_channel()->Call(\"Test\", request, &reply);\n\n        if (status == QNetworkReply::NoError) {\n            *rpcOK = true;\n            return reply;\n        } else {\n            NOT_OK\n            return reply;\n        }\n    }\n\n    libcore::UpdateResp Client::Update(bool *rpcOK, const libcore::UpdateReq &request) {\n        libcore::UpdateResp reply;\n        auto status = default_grpc_channel->Call(\"Update\", request, &reply);\n\n        if (status == QNetworkReply::NoError) {\n            *rpcOK = true;\n            return reply;\n        } else {\n            NOT_OK\n            return reply;\n        }\n    }\n} // namespace NekoGui_rpc\n\n#endif\n"
  },
  {
    "path": "rpc/gRPC.h",
    "content": "#pragma once\n\n#ifndef NKR_NO_GRPC\n\n#include \"go/grpc_server/gen/libcore.pb.h\"\n#include <QString>\n\nnamespace QtGrpc {\n    class Http2GrpcChannelPrivate;\n}\n\nnamespace NekoGui_rpc {\n    class Client {\n    public:\n        explicit Client(std::function<void(const QString &)> onError, const QString &target, const QString &token);\n\n        void Exit();\n\n        bool KeepAlive();\n\n        // QString returns is error string\n\n        QString Start(bool *rpcOK, const libcore::LoadConfigReq &request);\n\n        QString Stop(bool *rpcOK);\n\n        long long QueryStats(const std::string &tag, const std::string &direct);\n\n        std::string ListConnections();\n\n        libcore::TestResp Test(bool *rpcOK, const libcore::TestReq &request);\n\n        libcore::UpdateResp Update(bool *rpcOK, const libcore::UpdateReq &request);\n\n    private:\n        std::function<std::unique_ptr<QtGrpc::Http2GrpcChannelPrivate>()> make_grpc_channel;\n        std::unique_ptr<QtGrpc::Http2GrpcChannelPrivate> default_grpc_channel;\n        std::function<void(const QString &)> onError;\n    };\n\n    inline Client *defaultClient;\n} // namespace NekoGui_rpc\n#endif\n"
  },
  {
    "path": "sub/GroupUpdater.cpp",
    "content": "#include \"db/ProfileFilter.hpp\"\n#include \"fmt/includes.h\"\n#include \"fmt/Preset.hpp\"\n#include \"main/HTTPRequestHelper.hpp\"\n\n#include \"GroupUpdater.hpp\"\n\n#include <QInputDialog>\n#include <QUrlQuery>\n\n#ifndef NKR_NO_YAML\n\n#include <yaml-cpp/yaml.h>\n\n#endif\n\nnamespace NekoGui_sub {\n\n    GroupUpdater *groupUpdater = new GroupUpdater;\n\n    void RawUpdater_FixEnt(const std::shared_ptr<NekoGui::ProxyEntity> &ent) {\n        if (ent == nullptr) return;\n        auto stream = NekoGui_fmt::GetStreamSettings(ent->bean.get());\n        if (stream == nullptr) return;\n        // 1. \"security\"\n        if (stream->security == \"none\" || stream->security == \"0\" || stream->security == \"false\") {\n            stream->security = \"\";\n        } else if (stream->security == \"1\" || stream->security == \"true\") {\n            stream->security = \"tls\";\n        }\n        // 2. TLS SNI: v2rayN config builder generate sni like this, so set sni here for their format.\n        if (stream->security == \"tls\" && IsIpAddress(ent->bean->serverAddress) && (!stream->host.isEmpty()) && stream->sni.isEmpty()) {\n            stream->sni = stream->host;\n        }\n    }\n\n    void RawUpdater::update(const QString &str) {\n        // Base64 encoded subscription\n        if (auto str2 = DecodeB64IfValid(str); !str2.isEmpty()) {\n            update(str2);\n            return;\n        }\n\n        // Clash\n        if (str.contains(\"proxies:\")) {\n            updateClash(str);\n            return;\n        }\n\n        // Multi line\n        if (str.count(\"\\n\") > 0) {\n            auto list = str.split(\"\\n\");\n            for (const auto &str2: list) {\n                update(str2.trimmed());\n            }\n            return;\n        }\n\n        std::shared_ptr<NekoGui::ProxyEntity> ent;\n        bool needFix = true;\n\n        // Nekoray format\n        if (str.startsWith(\"nekoray://\")) {\n            needFix = false;\n            auto link = QUrl(str);\n            if (!link.isValid()) return;\n            ent = NekoGui::ProfileManager::NewProxyEntity(link.host());\n            if (ent->bean->version == -114514) return;\n            auto j = DecodeB64IfValid(link.fragment().toUtf8(), QByteArray::Base64UrlEncoding);\n            if (j.isEmpty()) return;\n            ent->bean->FromJsonBytes(j);\n        }\n\n        // SOCKS\n        if (str.startsWith(\"socks5://\") || str.startsWith(\"socks4://\") ||\n            str.startsWith(\"socks4a://\") || str.startsWith(\"socks://\")) {\n            ent = NekoGui::ProfileManager::NewProxyEntity(\"socks\");\n            auto ok = ent->SocksHTTPBean()->TryParseLink(str);\n            if (!ok) return;\n        }\n\n        // HTTP\n        if (str.startsWith(\"http://\") || str.startsWith(\"https://\")) {\n            ent = NekoGui::ProfileManager::NewProxyEntity(\"http\");\n            auto ok = ent->SocksHTTPBean()->TryParseLink(str);\n            if (!ok) return;\n        }\n\n        // ShadowSocks\n        if (str.startsWith(\"ss://\")) {\n            ent = NekoGui::ProfileManager::NewProxyEntity(\"shadowsocks\");\n            auto ok = ent->ShadowSocksBean()->TryParseLink(str);\n            if (!ok) return;\n        }\n\n        // VMess\n        if (str.startsWith(\"vmess://\")) {\n            ent = NekoGui::ProfileManager::NewProxyEntity(\"vmess\");\n            auto ok = ent->VMessBean()->TryParseLink(str);\n            if (!ok) return;\n        }\n\n        // VLESS\n        if (str.startsWith(\"vless://\")) {\n            ent = NekoGui::ProfileManager::NewProxyEntity(\"vless\");\n            auto ok = ent->TrojanVLESSBean()->TryParseLink(str);\n            if (!ok) return;\n        }\n\n        // Trojan\n        if (str.startsWith(\"trojan://\")) {\n            ent = NekoGui::ProfileManager::NewProxyEntity(\"trojan\");\n            auto ok = ent->TrojanVLESSBean()->TryParseLink(str);\n            if (!ok) return;\n        }\n\n        // Naive\n        if (str.startsWith(\"naive+\")) {\n            needFix = false;\n            ent = NekoGui::ProfileManager::NewProxyEntity(\"naive\");\n            auto ok = ent->NaiveBean()->TryParseLink(str);\n            if (!ok) return;\n        }\n\n        // Hysteria2\n        if (str.startsWith(\"hysteria2://\") || str.startsWith(\"hy2://\")) {\n            needFix = false;\n            ent = NekoGui::ProfileManager::NewProxyEntity(\"hysteria2\");\n            auto ok = ent->QUICBean()->TryParseLink(str);\n            if (!ok) return;\n        }\n\n        // TUIC\n        if (str.startsWith(\"tuic://\")) {\n            needFix = false;\n            ent = NekoGui::ProfileManager::NewProxyEntity(\"tuic\");\n            auto ok = ent->QUICBean()->TryParseLink(str);\n            if (!ok) return;\n        }\n\n        if (ent == nullptr) return;\n\n        // Fix\n        if (needFix) RawUpdater_FixEnt(ent);\n\n        // End\n        NekoGui::profileManager->AddProfile(ent, gid_add_to);\n        updated_order += ent;\n    }\n\n#ifndef NKR_NO_YAML\n\n    QString Node2QString(const YAML::Node &n, const QString &def = \"\") {\n        try {\n            return n.as<std::string>().c_str();\n        } catch (const YAML::Exception &ex) {\n            qDebug() << ex.what();\n            return def;\n        }\n    }\n\n    QStringList Node2QStringList(const YAML::Node &n) {\n        try {\n            if (n.IsSequence()) {\n                QStringList list;\n                for (auto item: n) {\n                    list << item.as<std::string>().c_str();\n                }\n                return list;\n            } else {\n                return {};\n            }\n        } catch (const YAML::Exception &ex) {\n            qDebug() << ex.what();\n            return {};\n        }\n    }\n\n    int Node2Int(const YAML::Node &n, const int &def = 0) {\n        try {\n            return n.as<int>();\n        } catch (const YAML::Exception &ex) {\n            qDebug() << ex.what();\n            return def;\n        }\n    }\n\n    bool Node2Bool(const YAML::Node &n, const bool &def = false) {\n        try {\n            return n.as<bool>();\n        } catch (const YAML::Exception &ex) {\n            try {\n                return n.as<int>();\n            } catch (const YAML::Exception &ex2) {\n                qDebug() << ex2.what();\n            }\n            qDebug() << ex.what();\n            return def;\n        }\n    }\n\n    // NodeChild returns the first defined children or Null Node\n    YAML::Node NodeChild(const YAML::Node &n, const std::list<std::string> &keys) {\n        for (const auto &key: keys) {\n            auto child = n[key];\n            if (child.IsDefined()) return child;\n        }\n        return {};\n    }\n\n#endif\n\n    // https://github.com/Dreamacro/clash/wiki/configuration\n    void RawUpdater::updateClash(const QString &str) {\n#ifndef NKR_NO_YAML\n        try {\n            auto proxies = YAML::Load(str.toStdString())[\"proxies\"];\n            for (auto proxy: proxies) {\n                auto type = Node2QString(proxy[\"type\"]).toLower();\n                auto type_clash = type;\n\n                if (type == \"ss\" || type == \"ssr\") type = \"shadowsocks\";\n                if (type == \"socks5\") type = \"socks\";\n\n                auto ent = NekoGui::ProfileManager::NewProxyEntity(type);\n                if (ent->bean->version == -114514) continue;\n                bool needFix = false;\n\n                // common\n                ent->bean->name = Node2QString(proxy[\"name\"]);\n                ent->bean->serverAddress = Node2QString(proxy[\"server\"]);\n                ent->bean->serverPort = Node2Int(proxy[\"port\"]);\n\n                if (type_clash == \"ss\") {\n                    auto bean = ent->ShadowSocksBean();\n                    bean->method = Node2QString(proxy[\"cipher\"]).replace(\"dummy\", \"none\");\n                    bean->password = Node2QString(proxy[\"password\"]);\n                    auto plugin_n = proxy[\"plugin\"];\n                    auto pluginOpts_n = proxy[\"plugin-opts\"];\n\n                    // UDP over TCP\n                    if (Node2Bool(proxy[\"udp-over-tcp\"])) {\n                        bean->uot = Node2Int(proxy[\"udp-over-tcp-version\"]);\n                        if (bean->uot == 0) bean->uot = 2;\n                    }\n\n                    if (plugin_n.IsDefined() && pluginOpts_n.IsDefined()) {\n                        QStringList ssPlugin;\n                        auto plugin = Node2QString(plugin_n);\n                        if (plugin == \"obfs\") {\n                            ssPlugin << \"obfs-local\";\n                            ssPlugin << \"obfs=\" + Node2QString(pluginOpts_n[\"mode\"]);\n                            ssPlugin << \"obfs-host=\" + Node2QString(pluginOpts_n[\"host\"]);\n                        } else if (plugin == \"v2ray-plugin\") {\n                            auto mode = Node2QString(pluginOpts_n[\"mode\"]);\n                            auto host = Node2QString(pluginOpts_n[\"host\"]);\n                            auto path = Node2QString(pluginOpts_n[\"path\"]);\n                            ssPlugin << \"v2ray-plugin\";\n                            if (!mode.isEmpty() && mode != \"websocket\") ssPlugin << \"mode=\" + mode;\n                            if (Node2Bool(pluginOpts_n[\"tls\"])) ssPlugin << \"tls\";\n                            if (!host.isEmpty()) ssPlugin << \"host=\" + host;\n                            if (!path.isEmpty()) ssPlugin << \"path=\" + path;\n                            // clash only: skip-cert-verify\n                            // clash only: headers\n                            // clash: mux=?\n                        }\n                        bean->plugin = ssPlugin.join(\";\");\n                    }\n\n                    // sing-mux\n                    auto smux = NodeChild(proxy, {\"smux\"});\n                    if (Node2Bool(smux[\"enabled\"])) bean->stream->multiplex_status = 1;\n                } else if (type == \"socks\" || type == \"http\") {\n                    auto bean = ent->SocksHTTPBean();\n                    bean->username = Node2QString(proxy[\"username\"]);\n                    bean->password = Node2QString(proxy[\"password\"]);\n                    if (Node2Bool(proxy[\"tls\"])) bean->stream->security = \"tls\";\n                    if (Node2Bool(proxy[\"skip-cert-verify\"])) bean->stream->allow_insecure = true;\n                } else if (type == \"trojan\" || type == \"vless\") {\n                    needFix = true;\n                    auto bean = ent->TrojanVLESSBean();\n                    if (type == \"vless\") {\n                        bean->flow = Node2QString(proxy[\"flow\"]);\n                        bean->password = Node2QString(proxy[\"uuid\"]);\n                        // meta packet encoding\n                        if (Node2Bool(proxy[\"packet-addr\"])) {\n                            bean->stream->packet_encoding = \"packetaddr\";\n                        } else {\n                            // For VLESS, default to use xudp\n                            bean->stream->packet_encoding = \"xudp\";\n                        }\n                    } else {\n                        bean->password = Node2QString(proxy[\"password\"]);\n                    }\n                    bean->stream->security = \"tls\";\n                    bean->stream->network = Node2QString(proxy[\"network\"], \"tcp\");\n                    bean->stream->sni = FIRST_OR_SECOND(Node2QString(proxy[\"sni\"]), Node2QString(proxy[\"servername\"]));\n                    bean->stream->alpn = Node2QStringList(proxy[\"alpn\"]).join(\",\");\n                    bean->stream->allow_insecure = Node2Bool(proxy[\"skip-cert-verify\"]);\n                    bean->stream->utlsFingerprint = Node2QString(proxy[\"client-fingerprint\"]);\n                    if (bean->stream->utlsFingerprint.isEmpty()) {\n                        bean->stream->utlsFingerprint = NekoGui::dataStore->utlsFingerprint;\n                    }\n\n                    // sing-mux\n                    auto smux = NodeChild(proxy, {\"smux\"});\n                    if (Node2Bool(smux[\"enabled\"])) bean->stream->multiplex_status = 1;\n\n                    // opts\n                    auto ws = NodeChild(proxy, {\"ws-opts\", \"ws-opt\"});\n                    if (ws.IsMap()) {\n                        auto headers = ws[\"headers\"];\n                        for (auto header: headers) {\n                            if (Node2QString(header.first).toLower() == \"host\") {\n                                bean->stream->host = Node2QString(header.second);\n                            }\n                        }\n                        bean->stream->path = Node2QString(ws[\"path\"]);\n                        bean->stream->ws_early_data_length = Node2Int(ws[\"max-early-data\"]);\n                        bean->stream->ws_early_data_name = Node2QString(ws[\"early-data-header-name\"]);\n                    }\n\n                    auto grpc = NodeChild(proxy, {\"grpc-opts\", \"grpc-opt\"});\n                    if (grpc.IsMap()) {\n                        bean->stream->path = Node2QString(grpc[\"grpc-service-name\"]);\n                    }\n\n                    auto reality = NodeChild(proxy, {\"reality-opts\"});\n                    if (reality.IsMap()) {\n                        bean->stream->reality_pbk = Node2QString(reality[\"public-key\"]);\n                        bean->stream->reality_sid = Node2QString(reality[\"short-id\"]);\n                    }\n                } else if (type == \"vmess\") {\n                    needFix = true;\n                    auto bean = ent->VMessBean();\n                    bean->uuid = Node2QString(proxy[\"uuid\"]);\n                    bean->aid = Node2Int(proxy[\"alterId\"]);\n                    bean->security = Node2QString(proxy[\"cipher\"], bean->security);\n                    bean->stream->network = Node2QString(proxy[\"network\"], \"tcp\").replace(\"h2\", \"http\");\n                    bean->stream->sni = FIRST_OR_SECOND(Node2QString(proxy[\"sni\"]), Node2QString(proxy[\"servername\"]));\n                    bean->stream->alpn = Node2QStringList(proxy[\"alpn\"]).join(\",\");\n                    if (Node2Bool(proxy[\"tls\"])) bean->stream->security = \"tls\";\n                    if (Node2Bool(proxy[\"skip-cert-verify\"])) bean->stream->allow_insecure = true;\n                    bean->stream->utlsFingerprint = Node2QString(proxy[\"client-fingerprint\"]);\n                    bean->stream->utlsFingerprint = Node2QString(proxy[\"client-fingerprint\"]);\n                    if (bean->stream->utlsFingerprint.isEmpty()) {\n                        bean->stream->utlsFingerprint = NekoGui::dataStore->utlsFingerprint;\n                    }\n\n                    // sing-mux\n                    auto smux = NodeChild(proxy, {\"smux\"});\n                    if (Node2Bool(smux[\"enabled\"])) bean->stream->multiplex_status = 1;\n\n                    // meta packet encoding\n                    if (Node2Bool(proxy[\"xudp\"])) bean->stream->packet_encoding = \"xudp\";\n                    if (Node2Bool(proxy[\"packet-addr\"])) bean->stream->packet_encoding = \"packetaddr\";\n\n                    // opts\n                    auto ws = NodeChild(proxy, {\"ws-opts\", \"ws-opt\"});\n                    if (ws.IsMap()) {\n                        auto headers = ws[\"headers\"];\n                        for (auto header: headers) {\n                            if (Node2QString(header.first).toLower() == \"host\") {\n                                bean->stream->host = Node2QString(header.second);\n                            }\n                        }\n                        bean->stream->path = Node2QString(ws[\"path\"]);\n                        bean->stream->ws_early_data_length = Node2Int(ws[\"max-early-data\"]);\n                        bean->stream->ws_early_data_name = Node2QString(ws[\"early-data-header-name\"]);\n                        // for Xray\n                        if (Node2QString(ws[\"early-data-header-name\"]) == \"Sec-WebSocket-Protocol\") {\n                            bean->stream->path += \"?ed=\" + Node2QString(ws[\"max-early-data\"]);\n                        }\n                    }\n\n                    auto grpc = NodeChild(proxy, {\"grpc-opts\", \"grpc-opt\"});\n                    if (grpc.IsMap()) {\n                        bean->stream->path = Node2QString(grpc[\"grpc-service-name\"]);\n                    }\n\n                    auto h2 = NodeChild(proxy, {\"h2-opts\", \"h2-opt\"});\n                    if (h2.IsMap()) {\n                        auto hosts = h2[\"host\"];\n                        for (auto host: hosts) {\n                            bean->stream->host = Node2QString(host);\n                            break;\n                        }\n                        bean->stream->path = Node2QString(h2[\"path\"]);\n                    }\n\n                    auto tcp_http = NodeChild(proxy, {\"http-opts\", \"http-opt\"});\n                    if (tcp_http.IsMap()) {\n                        bean->stream->network = \"tcp\";\n                        bean->stream->header_type = \"http\";\n                        auto headers = tcp_http[\"headers\"];\n                        for (auto header: headers) {\n                            if (Node2QString(header.first).toLower() == \"host\") {\n                                bean->stream->host = Node2QString(header.second[0]);\n                            }\n                            break;\n                        }\n                        auto paths = tcp_http[\"path\"];\n                        for (auto path: paths) {\n                            bean->stream->path = Node2QString(path);\n                            break;\n                        }\n                    }\n                } else if (type == \"hysteria2\") {\n                    auto bean = ent->QUICBean();\n\n                    bean->hopPort = Node2QString(proxy[\"ports\"]);\n\n                    bean->allowInsecure = Node2Bool(proxy[\"skip-cert-verify\"]);\n                    bean->caText = Node2QString(proxy[\"ca-str\"]);\n                    bean->sni = Node2QString(proxy[\"sni\"]);\n\n                    bean->obfsPassword = Node2QString(proxy[\"obfs-password\"]);\n                    bean->password = Node2QString(proxy[\"password\"]);\n\n                    bean->uploadMbps = Node2QString(proxy[\"up\"]).split(\" \")[0].toInt();\n                    bean->downloadMbps = Node2QString(proxy[\"down\"]).split(\" \")[0].toInt();\n                } else if (type == \"tuic\") {\n                    auto bean = ent->QUICBean();\n\n                    bean->uuid = Node2QString(proxy[\"uuid\"]);\n                    bean->password = Node2QString(proxy[\"password\"]);\n\n                    if (Node2Int(proxy[\"heartbeat-interval\"]) != 0) {\n                        bean->heartbeat = Int2String(Node2Int(proxy[\"heartbeat-interval\"])) + \"ms\";\n                    }\n\n                    bean->udpRelayMode = Node2QString(proxy[\"udp-relay-mode\"], bean->udpRelayMode);\n                    bean->congestionControl = Node2QString(proxy[\"congestion-controller\"], bean->congestionControl);\n\n                    bean->disableSni = Node2Bool(proxy[\"disable-sni\"]);\n                    bean->zeroRttHandshake = Node2Bool(proxy[\"reduce-rtt\"]);\n                    bean->allowInsecure = Node2Bool(proxy[\"skip-cert-verify\"]);\n                    bean->alpn = Node2QStringList(proxy[\"alpn\"]).join(\",\");\n                    bean->caText = Node2QString(proxy[\"ca-str\"]);\n                    bean->sni = Node2QString(proxy[\"sni\"]);\n\n                    if (Node2Bool(proxy[\"udp-over-stream\"])) bean->uos = true;\n\n                    if (!Node2QString(proxy[\"ip\"]).isEmpty()) {\n                        if (bean->sni.isEmpty()) bean->sni = bean->serverAddress;\n                        bean->serverAddress = Node2QString(proxy[\"ip\"]);\n                    }\n                } else {\n                    continue;\n                }\n\n                if (needFix) RawUpdater_FixEnt(ent);\n                NekoGui::profileManager->AddProfile(ent, gid_add_to);\n                updated_order += ent;\n            }\n        } catch (const YAML::Exception &ex) {\n            runOnUiThread([=] {\n                MessageBoxWarning(\"YAML Exception\", ex.what());\n            });\n        }\n#endif\n    }\n\n    // 在新的 thread 运行\n    void GroupUpdater::AsyncUpdate(const QString &str, int _sub_gid, const std::function<void()> &finish) {\n        auto content = str.trimmed();\n        bool asURL = false;\n        bool createNewGroup = false;\n\n        if (_sub_gid < 0 && (content.startsWith(\"http://\") || content.startsWith(\"https://\"))) {\n            auto items = QStringList{\n                QObject::tr(\"As Subscription (add to this group)\"),\n                QObject::tr(\"As Subscription (create new group)\"),\n                QObject::tr(\"As link\"),\n            };\n            bool ok;\n            auto a = QInputDialog::getItem(nullptr,\n                                           QObject::tr(\"url detected\"),\n                                           QObject::tr(\"%1\\nHow to update?\").arg(content),\n                                           items, 0, false, &ok);\n            if (!ok) return;\n            if (items.indexOf(a) <= 1) asURL = true;\n            if (items.indexOf(a) == 1) createNewGroup = true;\n        }\n\n        runOnNewThread([=] {\n            auto gid = _sub_gid;\n            if (createNewGroup) {\n                auto group = NekoGui::ProfileManager::NewGroup();\n                group->name = QUrl(str).host();\n                group->url = str;\n                NekoGui::profileManager->AddGroup(group);\n                gid = group->id;\n                MW_dialog_message(\"SubUpdater\", \"NewGroup\");\n            }\n            Update(str, gid, asURL);\n            emit asyncUpdateCallback(gid);\n            if (finish != nullptr) finish();\n        });\n    }\n\n    void GroupUpdater::Update(const QString &_str, int _sub_gid, bool _not_sub_as_url) {\n        // 创建 rawUpdater\n        NekoGui::dataStore->imported_count = 0;\n        auto rawUpdater = std::make_unique<RawUpdater>();\n        rawUpdater->gid_add_to = _sub_gid;\n\n        // 准备\n        QString sub_user_info;\n        bool asURL = _sub_gid >= 0 || _not_sub_as_url; // 把 _str 当作 url 处理（下载内容）\n        auto content = _str.trimmed();\n        auto group = NekoGui::profileManager->GetGroup(_sub_gid);\n        if (group != nullptr && group->archive) return;\n\n        // 网络请求\n        if (asURL) {\n            auto groupName = group == nullptr ? content : group->name;\n            MW_show_log(\">>>>>>>> \" + QObject::tr(\"Requesting subscription: %1\").arg(groupName));\n\n            auto resp = NetworkRequestHelper::HttpGet(content);\n            if (!resp.error.isEmpty()) {\n                MW_show_log(\"<<<<<<<< \" + QObject::tr(\"Requesting subscription %1 error: %2\").arg(groupName, resp.error + \"\\n\" + resp.data));\n                return;\n            }\n\n            content = resp.data;\n            sub_user_info = NetworkRequestHelper::GetHeader(resp.header, \"Subscription-UserInfo\");\n\n            MW_show_log(\"<<<<<<<< \" + QObject::tr(\"Subscription request fininshed: %1\").arg(groupName));\n        }\n\n        QList<std::shared_ptr<NekoGui::ProxyEntity>> in;          // 更新前\n        QList<std::shared_ptr<NekoGui::ProxyEntity>> out_all;     // 更新前 + 更新后\n        QList<std::shared_ptr<NekoGui::ProxyEntity>> out;         // 更新后\n        QList<std::shared_ptr<NekoGui::ProxyEntity>> only_in;     // 只在更新前有的\n        QList<std::shared_ptr<NekoGui::ProxyEntity>> only_out;    // 只在更新后有的\n        QList<std::shared_ptr<NekoGui::ProxyEntity>> update_del;  // 更新前后都有的，需要删除的新配置\n        QList<std::shared_ptr<NekoGui::ProxyEntity>> update_keep; // 更新前后都有的，被保留的旧配置\n\n        // 订阅解析前\n        if (group != nullptr) {\n            in = group->Profiles();\n            group->sub_last_update = QDateTime::currentMSecsSinceEpoch() / 1000;\n            group->info = sub_user_info;\n            group->order.clear();\n            group->Save();\n            //\n            if (NekoGui::dataStore->sub_clear) {\n                MW_show_log(QObject::tr(\"Clearing servers...\"));\n                for (const auto &profile: in) {\n                    NekoGui::profileManager->DeleteProfile(profile->id);\n                }\n            }\n        }\n\n        // 解析并添加 profile\n        rawUpdater->update(content);\n\n        if (group != nullptr) {\n            out_all = group->Profiles();\n\n            QString change_text;\n\n            if (NekoGui::dataStore->sub_clear) {\n                // all is new profile\n                for (const auto &ent: out_all) {\n                    change_text += \"[+] \" + ent->bean->DisplayTypeAndName() + \"\\n\";\n                }\n            } else {\n                // find and delete not updated profile by ProfileFilter\n                NekoGui::ProfileFilter::OnlyInSrc_ByPointer(out_all, in, out);\n                NekoGui::ProfileFilter::OnlyInSrc(in, out, only_in);\n                NekoGui::ProfileFilter::OnlyInSrc(out, in, only_out);\n                NekoGui::ProfileFilter::Common(in, out, update_keep, update_del, false);\n\n                QString notice_added;\n                QString notice_deleted;\n                for (const auto &ent: only_out) {\n                    notice_added += \"[+] \" + ent->bean->DisplayTypeAndName() + \"\\n\";\n                }\n                for (const auto &ent: only_in) {\n                    notice_deleted += \"[-] \" + ent->bean->DisplayTypeAndName() + \"\\n\";\n                }\n\n                // sort according to order in remote\n                group->order = {};\n                for (const auto &ent: rawUpdater->updated_order) {\n                    auto deleted_index = update_del.indexOf(ent);\n                    if (deleted_index > 0) {\n                        if (deleted_index >= update_keep.count()) continue; // should not happen\n                        auto ent2 = update_keep[deleted_index];\n                        group->order.append(ent2->id);\n                    } else {\n                        group->order.append(ent->id);\n                    }\n                }\n                group->Save();\n\n                // cleanup\n                for (const auto &ent: out_all) {\n                    if (!group->order.contains(ent->id)) {\n                        NekoGui::profileManager->DeleteProfile(ent->id);\n                    }\n                }\n\n                change_text = \"\\n\" + QObject::tr(\"Added %1 profiles:\\n%2\\nDeleted %3 Profiles:\\n%4\")\n                                         .arg(only_out.length())\n                                         .arg(notice_added)\n                                         .arg(only_in.length())\n                                         .arg(notice_deleted);\n                if (only_out.length() + only_in.length() == 0) change_text = QObject::tr(\"Nothing\");\n            }\n\n            MW_show_log(\"<<<<<<<< \" + QObject::tr(\"Change of %1:\").arg(group->name) + \"\\n\" + change_text);\n            MW_dialog_message(\"SubUpdater\", \"finish-dingyue\");\n        } else {\n            NekoGui::dataStore->imported_count = rawUpdater->updated_order.count();\n            MW_dialog_message(\"SubUpdater\", \"finish\");\n        }\n    }\n} // namespace NekoGui_sub\n\nbool UI_update_all_groups_Updating = false;\n\n#define should_skip_group(g) (g == nullptr || g->url.isEmpty() || g->archive || (onlyAllowed && g->skip_auto_update))\n\nvoid serialUpdateSubscription(const QList<int> &groupsTabOrder, int _order, bool onlyAllowed) {\n    if (_order >= groupsTabOrder.size()) {\n        UI_update_all_groups_Updating = false;\n        return;\n    }\n\n    // calculate this group\n    auto group = NekoGui::profileManager->GetGroup(groupsTabOrder[_order]);\n    if (group == nullptr || should_skip_group(group)) {\n        serialUpdateSubscription(groupsTabOrder, _order + 1, onlyAllowed);\n        return;\n    }\n\n    int nextOrder = _order + 1;\n    while (nextOrder < groupsTabOrder.size()) {\n        auto nextGid = groupsTabOrder[nextOrder];\n        auto nextGroup = NekoGui::profileManager->GetGroup(nextGid);\n        if (!should_skip_group(nextGroup)) {\n            break;\n        }\n        nextOrder += 1;\n    }\n\n    // Async update current group\n    UI_update_all_groups_Updating = true;\n    NekoGui_sub::groupUpdater->AsyncUpdate(group->url, group->id, [=] {\n        serialUpdateSubscription(groupsTabOrder, nextOrder, onlyAllowed);\n    });\n}\n\nvoid UI_update_all_groups(bool onlyAllowed) {\n    if (UI_update_all_groups_Updating) {\n        MW_show_log(\"The last subscription update has not exited.\");\n        return;\n    }\n\n    auto groupsTabOrder = NekoGui::profileManager->groupsTabOrder;\n    serialUpdateSubscription(groupsTabOrder, 0, onlyAllowed);\n}\n"
  },
  {
    "path": "sub/GroupUpdater.hpp",
    "content": "#pragma once\n\n#include \"db/Database.hpp\"\n\nnamespace NekoGui_sub {\n    class RawUpdater {\n    public:\n        void updateClash(const QString &str);\n\n        void update(const QString &str);\n\n        int gid_add_to = -1; // 导入到指定组 -1 为当前选中组\n\n        QList<std::shared_ptr<NekoGui::ProxyEntity>> updated_order; // 新增的配置，按照导入时处理的先后排序\n    };\n\n    class GroupUpdater : public QObject {\n        Q_OBJECT\n\n    public:\n        void AsyncUpdate(const QString &str, int _sub_gid = -1, const std::function<void()> &finish = nullptr);\n\n        void Update(const QString &_str, int _sub_gid = -1, bool _not_sub_as_url = false);\n\n    signals:\n\n        void asyncUpdateCallback(int gid);\n    };\n\n    extern GroupUpdater *groupUpdater;\n} // namespace NekoGui_sub\n\n// 更新所有订阅 关闭分组窗口时 更新动作继续执行\nvoid UI_update_all_groups(bool onlyAllowed = false);\n"
  },
  {
    "path": "sys/AutoRun.cpp",
    "content": "#include \"AutoRun.hpp\"\n\n#include <QApplication>\n#include <QDir>\n\n#include \"3rdparty/fix_old_qt.h\"\n#include \"main/NekoGui.hpp\"\n\n// macOS headers (possibly OBJ-c)\n#if defined(Q_OS_MACOS)\n#include <CoreFoundation/CoreFoundation.h>\n#include <CoreServices/CoreServices.h>\n#endif\n\n#ifdef Q_OS_WIN\n\n#include <QSettings>\n\nQString Windows_GenAutoRunString() {\n    auto appPath = QApplication::applicationFilePath();\n    appPath = \"\\\"\" + QDir::toNativeSeparators(appPath) + \"\\\"\";\n    appPath += \" -tray\";\n    return appPath;\n}\n\nvoid AutoRun_SetEnabled(bool enable) {\n    // 以程序名称作为注册表中的键\n    // 根据键获取对应的值（程序路径）\n    auto appPath = QApplication::applicationFilePath();\n    QFileInfo fInfo(appPath);\n    QString name = fInfo.baseName();\n\n    QSettings settings(\"HKEY_CURRENT_USER\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\", QSettings::NativeFormat);\n\n    if (enable) {\n        settings.setValue(name, Windows_GenAutoRunString());\n    } else {\n        settings.remove(name);\n    }\n}\n\nbool AutoRun_IsEnabled() {\n    QSettings settings(\"HKEY_CURRENT_USER\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\", QSettings::NativeFormat);\n\n    // 以程序名称作为注册表中的键\n    // 根据键获取对应的值（程序路径）\n    auto appPath = QApplication::applicationFilePath();\n    QFileInfo fInfo(appPath);\n    QString name = fInfo.baseName();\n\n    return settings.value(name).toString() == Windows_GenAutoRunString();\n}\n\n#endif\n\n#ifdef Q_OS_MACOS\n\nvoid AutoRun_SetEnabled(bool enable) {\n    // From\n    // https://github.com/nextcloud/desktop/blob/master/src/common/utility_mac.cpp\n    QString filePath = QDir(QCoreApplication::applicationDirPath() + QLatin1String(\"/../..\")).absolutePath();\n    CFStringRef folderCFStr = CFStringCreateWithCString(0, filePath.toUtf8().data(), kCFStringEncodingUTF8);\n    CFURLRef urlRef = CFURLCreateWithFileSystemPath(0, folderCFStr, kCFURLPOSIXPathStyle, true);\n    LSSharedFileListRef loginItems = LSSharedFileListCreate(0, kLSSharedFileListSessionLoginItems, 0);\n\n    if (loginItems && enable) {\n        // Insert an item to the list.\n        LSSharedFileListItemRef item =\n            LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemLast, 0, 0, urlRef, 0, 0);\n\n        if (item) CFRelease(item);\n\n        CFRelease(loginItems);\n    } else if (loginItems && !enable) {\n        // We need to iterate over the items and check which one is \"ours\".\n        UInt32 seedValue;\n        CFArrayRef itemsArray = LSSharedFileListCopySnapshot(loginItems, &seedValue);\n        CFStringRef appUrlRefString = CFURLGetString(urlRef);\n\n        for (int i = 0; i < CFArrayGetCount(itemsArray); i++) {\n            LSSharedFileListItemRef item = (LSSharedFileListItemRef) CFArrayGetValueAtIndex(itemsArray, i);\n            CFURLRef itemUrlRef = NULL;\n\n            if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) {\n                CFStringRef itemUrlString = CFURLGetString(itemUrlRef);\n\n                if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {\n                    LSSharedFileListItemRemove(loginItems, item); // remove it!\n                }\n\n                CFRelease(itemUrlRef);\n            }\n        }\n\n        CFRelease(itemsArray);\n        CFRelease(loginItems);\n    }\n\n    CFRelease(folderCFStr);\n    CFRelease(urlRef);\n}\n\nbool AutoRun_IsEnabled() {\n    // From\n    // https://github.com/nextcloud/desktop/blob/master/src/common/utility_mac.cpp\n    // this is quite some duplicate code with setLaunchOnStartup, at some\n    // point we should fix this FIXME.\n    bool returnValue = false;\n    QString filePath = QDir(QCoreApplication::applicationDirPath() + QLatin1String(\"/../..\")).absolutePath();\n    CFStringRef folderCFStr = CFStringCreateWithCString(0, filePath.toUtf8().data(), kCFStringEncodingUTF8);\n    CFURLRef urlRef = CFURLCreateWithFileSystemPath(0, folderCFStr, kCFURLPOSIXPathStyle, true);\n    LSSharedFileListRef loginItems = LSSharedFileListCreate(0, kLSSharedFileListSessionLoginItems, 0);\n\n    if (loginItems) {\n        // We need to iterate over the items and check which one is \"ours\".\n        UInt32 seedValue;\n        CFArrayRef itemsArray = LSSharedFileListCopySnapshot(loginItems, &seedValue);\n        CFStringRef appUrlRefString = CFURLGetString(urlRef); // no need for release\n\n        for (int i = 0; i < CFArrayGetCount(itemsArray); i++) {\n            LSSharedFileListItemRef item = (LSSharedFileListItemRef) CFArrayGetValueAtIndex(itemsArray, i);\n            CFURLRef itemUrlRef = NULL;\n\n            if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) {\n                CFStringRef itemUrlString = CFURLGetString(itemUrlRef);\n\n                if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {\n                    returnValue = true;\n                }\n\n                CFRelease(itemUrlRef);\n            }\n        }\n\n        CFRelease(itemsArray);\n    }\n\n    CFRelease(loginItems);\n    CFRelease(folderCFStr);\n    CFRelease(urlRef);\n    return returnValue;\n}\n\n#endif\n\n#ifdef Q_OS_LINUX\n\n#include <QStandardPaths>\n#include <QProcessEnvironment>\n#include <QTextStream>\n\n#define NEWLINE \"\\n\"\n\n//  launchatlogin.cpp\n//  ShadowClash\n//\n//  Created by TheWanderingCoel on 2018/6/12.\n//  Copyright © 2019 Coel Wu. All rights reserved.\n//\nQString getUserAutostartDir_private() {\n    QString config = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);\n    config += QLatin1String(\"/autostart/\");\n    return config;\n}\n\nvoid AutoRun_SetEnabled(bool enable) {\n    // From https://github.com/nextcloud/desktop/blob/master/src/common/utility_unix.cpp\n    QString appName = QCoreApplication::applicationName();\n    QString userAutoStartPath = getUserAutostartDir_private();\n    QString desktopFileLocation = userAutoStartPath + appName + QLatin1String(\".desktop\");\n    QStringList appCmdList;\n\n    // nekoray: launcher\n    if (qEnvironmentVariable(\"NKR_FROM_LAUNCHER\") == \"1\") {\n        appCmdList << QApplication::applicationDirPath() + \"/launcher\"\n                   << \"--\";\n    } else {\n        if (QProcessEnvironment::systemEnvironment().contains(\"APPIMAGE\")) {\n            appCmdList << QProcessEnvironment::systemEnvironment().value(\"APPIMAGE\");\n        } else {\n            appCmdList << QApplication::applicationFilePath();\n        }\n    }\n\n    appCmdList << \"-tray\";\n\n    if (NekoGui::dataStore->flag_use_appdata) {\n        appCmdList << \"-appdata\";\n    }\n\n    if (enable) {\n        if (!QDir().exists(userAutoStartPath) && !QDir().mkpath(userAutoStartPath)) {\n            // qCWarning(lcUtility) << \"Could not create autostart folder\"\n            // << userAutoStartPath;\n            return;\n        }\n\n        QFile iniFile(desktopFileLocation);\n\n        if (!iniFile.open(QIODevice::WriteOnly)) {\n            // qCWarning(lcUtility) << \"Could not write auto start entry\" <<\n            // desktopFileLocation;\n            return;\n        }\n\n        QTextStream ts(&iniFile);\n#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)\n        ts.setCodec(\"UTF-8\");\n#endif\n        ts << QLatin1String(\"[Desktop Entry]\") << NEWLINE\n           << QLatin1String(\"Name=\") << appName << NEWLINE\n           << QLatin1String(\"Exec=\") << appCmdList.join(\" \") << NEWLINE\n           << QLatin1String(\"Terminal=\") << \"false\" << NEWLINE\n           << QLatin1String(\"Categories=\") << \"Network\" << NEWLINE\n           << QLatin1String(\"Type=\") << \"Application\" << NEWLINE\n           << QLatin1String(\"StartupNotify=\") << \"false\" << NEWLINE\n           << QLatin1String(\"X-GNOME-Autostart-enabled=\") << \"true\" << NEWLINE;\n        ts.flush();\n        iniFile.close();\n    } else {\n        QFile::remove(desktopFileLocation);\n    }\n}\n\nbool AutoRun_IsEnabled() {\n    QString appName = QCoreApplication::applicationName();\n    QString desktopFileLocation = getUserAutostartDir_private() + appName + QLatin1String(\".desktop\");\n    return QFile::exists(desktopFileLocation);\n}\n\n#endif\n"
  },
  {
    "path": "sys/AutoRun.hpp",
    "content": "#pragma once\n\nvoid AutoRun_SetEnabled(bool enable);\n\nbool AutoRun_IsEnabled();\n"
  },
  {
    "path": "sys/ExternalProcess.cpp",
    "content": "#include \"ExternalProcess.hpp\"\n#include \"main/NekoGui.hpp\"\n\n#include <QTimer>\n#include <QDir>\n#include <QApplication>\n#include <QElapsedTimer>\n\nnamespace NekoGui_sys {\n\n    ExternalProcess::ExternalProcess() : QProcess() {\n        // qDebug() << \"[Debug] ExternalProcess()\" << this << running_ext;\n        this->env = QProcessEnvironment::systemEnvironment().toStringList();\n    }\n\n    ExternalProcess::~ExternalProcess() {\n        // qDebug() << \"[Debug] ~ExternalProcess()\" << this << running_ext;\n    }\n\n    void ExternalProcess::Start() {\n        if (started) return;\n        started = true;\n\n        if (managed) {\n            connect(this, &QProcess::readyReadStandardOutput, this, [&]() {\n                auto log = readAllStandardOutput();\n                if (logCounter.fetchAndAddRelaxed(log.count(\"\\n\")) > NekoGui::dataStore->max_log_line) return;\n                MW_show_log_ext_vt100(log);\n            });\n            connect(this, &QProcess::readyReadStandardError, this, [&]() {\n                MW_show_log_ext_vt100(readAllStandardError().trimmed());\n            });\n            connect(this, &QProcess::errorOccurred, this, [&](QProcess::ProcessError error) {\n                if (!killed) {\n                    crashed = true;\n                    MW_show_log_ext(tag, \"errorOccurred:\" + errorString());\n                    MW_dialog_message(\"ExternalProcess\", \"Crashed\");\n                }\n            });\n            connect(this, &QProcess::stateChanged, this, [&](QProcess::ProcessState state) {\n                if (state == QProcess::NotRunning) {\n                    if (killed) { // 用户命令退出\n                        MW_show_log_ext(tag, \"External core stopped\");\n                    } else if (!crashed) { // 异常退出\n                        crashed = true;\n                        MW_show_log_ext(tag, \"[Error] Program exited accidentally: \" + errorString());\n                        Kill();\n                        MW_dialog_message(\"ExternalProcess\", \"Crashed\");\n                    }\n                }\n            });\n            MW_show_log_ext(tag, \"External core starting: \" + env.join(\" \") + \" \" + program + \" \" + arguments.join(\" \"));\n        }\n\n        QProcess::setEnvironment(env);\n        QProcess::start(program, arguments);\n    }\n\n    void ExternalProcess::Kill() {\n        if (killed) return;\n        killed = true;\n\n        if (!crashed) {\n            QProcess::kill();\n            QProcess::waitForFinished(500);\n        }\n    }\n\n    //\n\n    QElapsedTimer coreRestartTimer;\n\n    CoreProcess::CoreProcess(const QString &core_path, const QStringList &args) : ExternalProcess() {\n        ExternalProcess::managed = false;\n        ExternalProcess::program = core_path;\n        ExternalProcess::arguments = args;\n\n        connect(this, &QProcess::readyReadStandardOutput, this, [&]() {\n            auto log = readAllStandardOutput();\n            if (!NekoGui::dataStore->core_running) {\n                if (log.contains(\"grpc server listening\")) {\n                    // The core really started\n                    NekoGui::dataStore->core_running = true;\n                    if (start_profile_when_core_is_up >= 0) {\n                        MW_dialog_message(\"ExternalProcess\", \"CoreStarted,\" + Int2String(start_profile_when_core_is_up));\n                        start_profile_when_core_is_up = -1;\n                    }\n                } else if (log.contains(\"failed to serve\")) {\n                    // The core failed to start\n                    QProcess::kill();\n                }\n            }\n            if (logCounter.fetchAndAddRelaxed(log.count(\"\\n\")) > NekoGui::dataStore->max_log_line) return;\n            MW_show_log(log);\n        });\n        connect(this, &QProcess::readyReadStandardError, this, [&]() {\n            auto log = readAllStandardError().trimmed();\n            if (show_stderr) {\n                MW_show_log(log);\n                return;\n            }\n            if (log.contains(\"token is set\")) {\n                show_stderr = true;\n            }\n        });\n        connect(this, &QProcess::errorOccurred, this, [&](QProcess::ProcessError error) {\n            if (error == QProcess::ProcessError::FailedToStart) {\n                failed_to_start = true;\n                MW_show_log(\"start core error occurred: \" + errorString() + \"\\n\");\n            }\n        });\n        connect(this, &QProcess::stateChanged, this, [&](QProcess::ProcessState state) {\n            if (state == QProcess::NotRunning) {\n                NekoGui::dataStore->core_running = false;\n            }\n\n            if (!NekoGui::dataStore->prepare_exit && state == QProcess::NotRunning) {\n                if (failed_to_start) return; // no retry\n                if (restarting) return;\n\n                MW_dialog_message(\"ExternalProcess\", \"CoreCrashed\");\n\n                // Retry rate limit\n                if (coreRestartTimer.isValid()) {\n                    if (coreRestartTimer.restart() < 10 * 1000) {\n                        coreRestartTimer = QElapsedTimer();\n                        MW_show_log(\"[Error] \" + QObject::tr(\"Core exits too frequently, stop automatic restart this profile.\"));\n                        return;\n                    }\n                } else {\n                    coreRestartTimer.start();\n                }\n\n                // Restart\n                start_profile_when_core_is_up = NekoGui::dataStore->started_id;\n                MW_show_log(\"[Error] \" + QObject::tr(\"Core exited, restarting.\"));\n                setTimeout([=] { Restart(); }, this, 1000);\n            }\n        });\n    }\n\n    void CoreProcess::Start() {\n        show_stderr = false;\n        // cwd: same as GUI, at ./config\n        ExternalProcess::Start();\n        write((NekoGui::dataStore->core_token + \"\\n\").toUtf8());\n    }\n\n    void CoreProcess::Restart() {\n        restarting = true;\n        QProcess::kill();\n        QProcess::waitForFinished(500);\n        ExternalProcess::started = false;\n        Start();\n        restarting = false;\n    }\n\n} // namespace NekoGui_sys\n"
  },
  {
    "path": "sys/ExternalProcess.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <QProcess>\n\nnamespace NekoGui_sys {\n    class ExternalProcess : public QProcess {\n    public:\n        QString tag;\n        QString program;\n        QStringList arguments;\n        QStringList env;\n\n        bool managed = true; // MW_dialog_message\n\n        ExternalProcess();\n        ~ExternalProcess();\n\n        // start & kill is one time\n\n        virtual void Start();\n\n        void Kill();\n\n    protected:\n        bool started = false;\n        bool killed = false;\n        bool crashed = false;\n    };\n\n    class CoreProcess : public ExternalProcess {\n    public:\n        CoreProcess(const QString &core_path, const QStringList &args);\n\n        void Start() override;\n\n        void Restart();\n\n        int start_profile_when_core_is_up = -1;\n\n    private:\n        bool show_stderr = false;\n        bool failed_to_start = false;\n        bool restarting = false;\n    };\n\n    // 手动管理\n    inline std::list<std::shared_ptr<ExternalProcess>> running_ext;\n\n    inline QAtomicInt logCounter;\n} // namespace NekoGui_sys\n"
  },
  {
    "path": "sys/linux/LinuxCap.cpp",
    "content": "#include \"LinuxCap.h\"\n\n#include <QDebug>\n#include <QProcess>\n#include <QStandardPaths>\n\n#define EXIT_CODE(p) (p.exitStatus() == QProcess::NormalExit ? p.exitCode() : -1)\n\nQString Linux_GetCapString(const QString &path) {\n    QProcess p;\n    p.setProgram(Linux_FindCapProgsExec(\"getcap\"));\n    p.setArguments({path});\n    p.start();\n    p.waitForFinished(500);\n    return p.readAllStandardOutput();\n}\n\nint Linux_Pkexec_SetCapString(const QString &path, const QString &cap) {\n    QProcess p;\n    p.setProgram(\"pkexec\");\n    p.setArguments({Linux_FindCapProgsExec(\"setcap\"), cap, path});\n    p.start();\n    p.waitForFinished(-1);\n    return EXIT_CODE(p);\n}\n\nbool Linux_HavePkexec() {\n    QProcess p;\n    p.setProgram(\"pkexec\");\n    p.setArguments({\"--help\"});\n    p.setProcessChannelMode(QProcess::SeparateChannels);\n    p.start();\n    p.waitForFinished(500);\n    return EXIT_CODE(p) == 0;\n}\n\nQString Linux_FindCapProgsExec(const QString &name) {\n    QString exec = QStandardPaths::findExecutable(name);\n    if (exec.isEmpty())\n        exec = QStandardPaths::findExecutable(name, {\"/usr/sbin\", \"/sbin\"});\n\n    if (exec.isEmpty())\n        qDebug() << \"Executable\" << name << \"could not be resolved\";\n    else\n        qDebug() << \"Found exec\" << name << \"at\" << exec;\n\n    return exec.isEmpty() ? name : exec;\n}\n"
  },
  {
    "path": "sys/linux/LinuxCap.h",
    "content": "#pragma once\n\n#include <QString>\n\nQString Linux_GetCapString(const QString &path);\n\nint Linux_Pkexec_SetCapString(const QString &path, const QString &cap);\n\nbool Linux_HavePkexec();\n\nQString Linux_FindCapProgsExec(const QString &name);\n"
  },
  {
    "path": "sys/windows/MiniDump.cpp",
    "content": "#include \"MiniDump.h\"\n\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#include <windows.h>\n#include <tchar.h>\n#include <dbghelp.h>\n\n#include <QApplication>\n#include <QDir>\n#include <QDateTime>\n#include <QMessageBox>\n\ntypedef BOOL(WINAPI *MINIDUMPWRITEDUMP)(\n    HANDLE hProcess,\n    DWORD dwPid,\n    HANDLE hFile,\n    MINIDUMP_TYPE DumpType,\n    CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,\n    CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,\n    CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);\n\nLONG __stdcall CreateCrashHandler(EXCEPTION_POINTERS *pException) {\n    QDir::setCurrent(QApplication::applicationDirPath());\n\n    HMODULE DllHandle = NULL;\n    DllHandle = LoadLibrary(_T(\"DBGHELP.DLL\"));\n\n    if (DllHandle) {\n        MINIDUMPWRITEDUMP Dump = (MINIDUMPWRITEDUMP) GetProcAddress(DllHandle, \"MiniDumpWriteDump\");\n        if (Dump) {\n            // 创建 Dump 文件\n            QDateTime CurDTime = QDateTime::currentDateTime();\n            QString current_date = CurDTime.toString(\"yyyy_MM_dd_hh_mm_ss\");\n            // dmp文件的命名\n            QString dumpText = \"Dump_\" + current_date + \".dmp\";\n            EXCEPTION_RECORD *record = pException->ExceptionRecord;\n            QString errCode(QString::number(record->ExceptionCode, 16));\n            QString errAddr(QString::number((uintptr_t) record->ExceptionAddress, 16));\n            QString errFlag(QString::number(record->ExceptionFlags, 16));\n            QString errPara(QString::number(record->NumberParameters, 16));\n            HANDLE DumpHandle = CreateFile((LPCWSTR) dumpText.utf16(),\n                                           GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);\n            if (DumpHandle != INVALID_HANDLE_VALUE) {\n                MINIDUMP_EXCEPTION_INFORMATION dumpInfo;\n                dumpInfo.ExceptionPointers = pException;\n                dumpInfo.ThreadId = GetCurrentThreadId();\n                dumpInfo.ClientPointers = TRUE;\n                // 将dump信息写入dmp文件\n                Dump(GetCurrentProcess(), GetCurrentProcessId(), DumpHandle, MiniDumpNormal, &dumpInfo,\n                     NULL, NULL);\n                CloseHandle(DumpHandle);\n            } else {\n                dumpText = \"\";\n            }\n            // 创建消息提示\n            QMessageBox::warning(NULL, \"Application crashed\",\n                                 QStringLiteral(\"ErrorCode: %1 ErrorAddr:%2 ErrorFlag: %3 ErrorPara: %4\\nVersion: %5\\nDump file at %6\")\n                                     .arg(errCode)\n                                     .arg(errAddr)\n                                     .arg(errFlag)\n                                     .arg(errPara)\n                                     .arg(NKR_VERSION)\n                                     .arg(dumpText),\n                                 QMessageBox::Ok);\n        }\n    }\n    return EXCEPTION_EXECUTE_HANDLER;\n}\n\nvoid Windows_SetCrashHandler() {\n    SetErrorMode(SEM_FAILCRITICALERRORS);\n    SetUnhandledExceptionFilter(CreateCrashHandler);\n}\n"
  },
  {
    "path": "sys/windows/MiniDump.h",
    "content": "#pragma once\n\nvoid Windows_SetCrashHandler();\n"
  },
  {
    "path": "sys/windows/guihelper.cpp",
    "content": "\n#include \"guihelper.h\"\n\n#include <QWidget>\n\n#include <windows.h>\n#include <shlobj.h>\n\nvoid Windows_QWidget_SetForegroundWindow(QWidget *w) {\n    HWND hForgroundWnd = GetForegroundWindow();\n    DWORD dwForeID = ::GetWindowThreadProcessId(hForgroundWnd, NULL);\n    DWORD dwCurID = ::GetCurrentThreadId();\n    ::AttachThreadInput(dwCurID, dwForeID, TRUE);\n    ::SetForegroundWindow((HWND) w->winId());\n    ::AttachThreadInput(dwCurID, dwForeID, FALSE);\n}\n\nint isThisAdmin = -1; // cached\n\nbool Windows_IsInAdmin() {\n    if (isThisAdmin >= 0) return isThisAdmin;\n    isThisAdmin = IsUserAnAdmin();\n    return isThisAdmin;\n}\n"
  },
  {
    "path": "sys/windows/guihelper.h",
    "content": "#pragma once\n\nclass QWidget;\n\nvoid Windows_QWidget_SetForegroundWindow(QWidget* w);\n\nbool Windows_IsInAdmin();\n"
  },
  {
    "path": "test/test-qt512-sdk-build.sh",
    "content": "QT=$PWD/qtsdk/5.12.12/gcc_64\nBUILD=build-sdk-qt512\nrm -rf $BUILD\nmkdir -p $BUILD\ncd $BUILD\ncmake -GNinja -DCMAKE_PREFIX_PATH=$QT ..\nninja\n"
  },
  {
    "path": "test/test-qt6-build.sh",
    "content": "rm -rf build-qt6\nmkdir -p build-qt6\ncd build-qt6\ncmake -GNinja -DQT_VERSION_MAJOR=6 ..\nninja\n"
  },
  {
    "path": "translations/fa_IR.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"fa_IR\" sourcelanguage=\"en\">\n<context>\n    <name>DialogBasicSettings</name>\n    <message>\n        <source>Basic Settings</source>\n        <translation>تنظیمات پایه</translation>\n    </message>\n    <message>\n        <source>Enable</source>\n        <translation>فعال کردن</translation>\n    </message>\n    <message>\n        <source>Listen Address</source>\n        <translation>آدرس درحال شنود</translation>\n    </message>\n    <message>\n        <source>concurrency</source>\n        <translation>همزمانی</translation>\n    </message>\n    <message>\n        <source>User Agent</source>\n        <translation>عامل کاربر</translation>\n    </message>\n    <message>\n        <source>Common</source>\n        <translation>متداول</translation>\n    </message>\n    <message>\n        <source>Style</source>\n        <translation>استایل</translation>\n    </message>\n    <message>\n        <source>Theme</source>\n        <translation>پوسته</translation>\n    </message>\n    <message>\n        <source>System</source>\n        <translation>سیستم</translation>\n    </message>\n    <message>\n        <source>Subscription</source>\n        <translation>اشتراک</translation>\n    </message>\n    <message>\n        <source>Core</source>\n        <translation>هسته</translation>\n    </message>\n    <message>\n        <source>Extra Core</source>\n        <translation>هسته اضافی</translation>\n    </message>\n    <message>\n        <source>Select</source>\n        <translation>برگزیدن</translation>\n    </message>\n    <message>\n        <source>Edit</source>\n        <translation>ویرایش</translation>\n    </message>\n    <message>\n        <source>Custom Inbound</source>\n        <translation>ورودی سفارشی</translation>\n    </message>\n    <message>\n        <source>Concurrent</source>\n        <translation>هم زمان</translation>\n    </message>\n    <message>\n        <source>Use proxy when updating subscription</source>\n        <translation>استفاده از پروکسی زمانی که اشتراک را بروزرسانی می کنید</translation>\n    </message>\n    <message>\n        <source>Security</source>\n        <translatorcomment>امنیت security</translatorcomment>\n        <translation>امنیت</translation>\n    </message>\n    <message>\n        <source>Statistics refresh rate</source>\n        <translation>نرخ تازه سازی آمار ترافیک</translation>\n    </message>\n    <message>\n        <source>Off</source>\n        <translation>خاموش</translation>\n    </message>\n    <message>\n        <source>Add</source>\n        <translation>اضافه کردن</translation>\n    </message>\n    <message>\n        <source>Delete</source>\n        <translatorcomment>حذف</translatorcomment>\n        <translation>حذف کردن</translation>\n    </message>\n    <message>\n        <source>Please input the core name.</source>\n        <translation>لطفا نام هسته را وارد کنید</translation>\n    </message>\n    <message>\n        <source>Please select the core name.</source>\n        <translation>لطفا نام هسته را انتخاب کنید</translation>\n    </message>\n    <message>\n        <source>Connection statistics</source>\n        <translation>آمار اتصال</translation>\n    </message>\n    <message>\n        <source>Include Pre-release when checking update</source>\n        <translation>هنگام بررسی به‌روزرسانی، نسخه پیش‌انتشار را نیز لحاظ شود</translation>\n    </message>\n    <message>\n        <source>System proxy format</source>\n        <translation>فرمت پروکسی سیستم</translation>\n    </message>\n    <message>\n        <source>Set custom icon</source>\n        <translation>تنظیم آیکون سفارشی</translation>\n    </message>\n    <message>\n        <source>Hide dashboard at startup</source>\n        <translation>مخفی کردن داشبورد هنگام راه‌اندازی</translation>\n    </message>\n    <message>\n        <source>Clear servers before updating subscription</source>\n        <translation>قبل از به‌روزرسانی اشتراک، سرورها را پاک شود</translation>\n    </message>\n    <message>\n        <source>Ignore TLS errors when updating subscription</source>\n        <translation>هنگام به‌روزرسانی اشتراک، خطاهای TLS را نادیده گرفته شود</translation>\n    </message>\n    <message>\n        <source>Advanced system proxy settings. Please select a format.</source>\n        <translation>تنظیمات پیشرفته پروکسی سیستم. لطفا یک قالب را انتخاب کنید.</translation>\n    </message>\n    <message>\n        <source>Please select a PNG file.</source>\n        <translation>لطفاً یک فایل PNG انتخاب کنید.</translation>\n    </message>\n    <message>\n        <source>Reset</source>\n        <translation>بازنشانی</translation>\n    </message>\n    <message>\n        <source>Cancel</source>\n        <translation>لغو کردن</translation>\n    </message>\n    <message>\n        <source>Please select a valid square image.</source>\n        <translation>لطفاً یک تصویر مربع معتبر انتخاب کنید.</translation>\n    </message>\n    <message>\n        <source>Max log lines</source>\n        <translation>حداکثر خطوط فایل لاگ</translation>\n    </message>\n    <message>\n        <source>Inbound Auth</source>\n        <translation>اعتبار ورودی</translation>\n    </message>\n    <message>\n        <source>Username</source>\n        <translation>نام کاربری</translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>رمز عبور</translation>\n    </message>\n    <message>\n        <source>Skip TLS certificate authentication by default (allowInsecure)</source>\n        <translation>رد شدن از احراز هویت گواهی TLS به طور پیش فرض (allowInsecure)</translation>\n    </message>\n    <message>\n        <source>Default uTLS Fingerprint</source>\n        <translation type=\"unfinished\">اثرانگشت پیشفرض uTLS</translation>\n    </message>\n    <message>\n        <source>Core Options</source>\n        <translation type=\"unfinished\">تنظیمات هسته</translation>\n    </message>\n    <message>\n        <source>Override underlying DNS</source>\n        <translation type=\"unfinished\">لغو دی ان اس زیربنایی</translation>\n    </message>\n    <message>\n        <source>Default On</source>\n        <translation type=\"unfinished\">به صورت پیشفرض فعال</translation>\n    </message>\n    <message>\n        <source>Multiplex (mux)</source>\n        <translation type=\"unfinished\">Multiplex (mux)</translation>\n    </message>\n    <message>\n        <source>Latency Test URL</source>\n        <translation type=\"unfinished\">آدرس تست تاخیر</translation>\n    </message>\n    <message>\n        <source>Download Test URL</source>\n        <translation type=\"unfinished\">آدرس تست دانلود</translation>\n    </message>\n    <message>\n        <source>Timeout (s)</source>\n        <translation type=\"unfinished\">تایم اوت (به ثانیه)</translation>\n    </message>\n    <message>\n        <source>Automatic update</source>\n        <translation type=\"unfinished\">آپدیت اتوماتیک</translation>\n    </message>\n    <message>\n        <source>Interval (minute, invalid if less than 30)</source>\n        <translation type=\"unfinished\">فاصله (به دقیقا ، اگر کمتر از ۳۰ باشد نادرست است)</translation>\n    </message>\n    <message>\n        <source>Share VMess Link with v2rayN Format</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Old Share Link Format</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Mixed (SOCKS+HTTP) Listen Port</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>DialogEditGroup</name>\n    <message>\n        <source>Edit Group</source>\n        <translation>ویرایش گروه</translation>\n    </message>\n    <message>\n        <source>Type</source>\n        <translation>نوع</translation>\n    </message>\n    <message>\n        <source>Name</source>\n        <translation>نام</translation>\n    </message>\n    <message>\n        <source>Basic</source>\n        <translation>پایه</translation>\n    </message>\n    <message>\n        <source>Subscription</source>\n        <translation>اشتراک</translation>\n    </message>\n    <message>\n        <source>Archive</source>\n        <translation>بایگانی</translation>\n    </message>\n    <message>\n        <source>URL</source>\n        <translation>URL</translation>\n    </message>\n    <message>\n        <source>Copy profile share links</source>\n        <translation>لینک های اشتراک گذاری نمایه را کپی کنید</translation>\n    </message>\n    <message>\n        <source>Copy profile share links (Neko Links)</source>\n        <translation>لینک های اشتراک گذاری نمایه را کپی کنید (لینک های Neko)</translation>\n    </message>\n    <message>\n        <source>Warning</source>\n        <translation>هشدار</translation>\n    </message>\n    <message>\n        <source>Please input URL</source>\n        <translation>لطفا URL را وارد کنید</translation>\n    </message>\n    <message>\n        <source>Copied</source>\n        <translation>کپی شده است</translation>\n    </message>\n    <message>\n        <source>Manually column width</source>\n        <translation>عرض ستون به صورت دستی</translation>\n    </message>\n    <message>\n        <source>Front Proxy</source>\n        <translation type=\"unfinished\">پروکسی front</translation>\n    </message>\n    <message>\n        <source>None</source>\n        <translation type=\"unfinished\">هیچ یک</translation>\n    </message>\n    <message>\n        <source>Clear</source>\n        <translation type=\"unfinished\">پاک کردن</translation>\n    </message>\n    <message>\n        <source>Skip automatic update</source>\n        <translation type=\"unfinished\">لغو آپدیت اتوماتیک</translation>\n    </message>\n    <message>\n        <source>Common</source>\n        <translation type=\"unfinished\">متداول</translation>\n    </message>\n    <message>\n        <source>Share</source>\n        <translation type=\"unfinished\">اشتراک گذاری</translation>\n    </message>\n</context>\n<context>\n    <name>DialogEditProfile</name>\n    <message>\n        <source>Edit</source>\n        <translation>ویرایش کردن</translation>\n    </message>\n    <message>\n        <source>Common</source>\n        <translation>متداول</translation>\n    </message>\n    <message>\n        <source>Type</source>\n        <translation>نوع</translation>\n    </message>\n    <message>\n        <source>Port</source>\n        <translation>پورت</translation>\n    </message>\n    <message>\n        <source>Address</source>\n        <translation>آدرس</translation>\n    </message>\n    <message>\n        <source>Name</source>\n        <translation>اسم</translation>\n    </message>\n    <message>\n        <source>Settings</source>\n        <translation>تنظیمات</translation>\n    </message>\n    <message>\n        <source>The underlying transport method. It must be consistent with the server, otherwise, the connection cannot be established.</source>\n        <translation>روش انتقال باید با سرور سازگار باشد، در غیر این صورت، اتصال نمی تواند برقرار شود.</translation>\n    </message>\n    <message>\n        <source>Network</source>\n        <translation>شبکه</translation>\n    </message>\n    <message>\n        <source>Transport Layer Security. It must be consistent with the server, otherwise, the connection cannot be established.</source>\n        <translation>امنیت لایه انتقال باید با سرور سازگار باشد، در غیر این صورت، اتصال نمی تواند برقرار شود.</translation>\n    </message>\n    <message>\n        <source>Security</source>\n        <translation>امنیت</translation>\n    </message>\n    <message>\n        <source>UDP FullCone Packet encoding for implementing features such as UDP FullCone. Server support is required, if the wrong selection is made, the connection cannot be made. Please leave it blank.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Packet Encoding</source>\n        <translation>رمزنگاری بسته ها</translation>\n    </message>\n    <message>\n        <source>Network Settings (%1)</source>\n        <translation>تنظیمات شبکه (1%)</translation>\n    </message>\n    <message>\n        <source>When enabled, V2Ray will not check the validity of the TLS certificate provided by the remote host (the security is equivalent to plaintext)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Allow insecure</source>\n        <translation>اجازه ناامن بودن</translation>\n    </message>\n    <message>\n        <source>Certificate</source>\n        <translation>گواهی</translation>\n    </message>\n    <message>\n        <source>Server name indication, clear text.</source>\n        <translation type=\"unfinished\">شناسه نام سرور ، متن صریح</translation>\n    </message>\n    <message>\n        <source>Application layer protocol negotiation, clear text. Please separate them with commas.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Custom (Extra Core)</source>\n        <translation>سفارشی ( هسته اضافه)</translation>\n    </message>\n    <message>\n        <source>Not set</source>\n        <translation>تنظیم نشده</translation>\n    </message>\n    <message>\n        <source>Already set</source>\n        <translation>تنظیم شده</translation>\n    </message>\n    <message>\n        <source>TLS Security Settings</source>\n        <translation type=\"unfinished\">تنظیمات امنیت TLS</translation>\n    </message>\n    <message>\n        <source>TLS Camouflage Settings</source>\n        <translation type=\"unfinished\">تنظیمات استتار TLS</translation>\n    </message>\n    <message>\n        <source>Reality public key. If not empty, turn TLS into REALITY.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Custom (%1 outbound)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Custom (%1 config)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Custom Outbound Settings</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Custom Config Settings</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Apply settings to this group</source>\n        <translation type=\"unfinished\">تنظیمات به این گروه اعمال شود</translation>\n    </message>\n    <message>\n        <source>Multiplex</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Keep Default</source>\n        <translation type=\"unfinished\">نگه داری مقدار پیشفرض</translation>\n    </message>\n    <message>\n        <source>On</source>\n        <translation type=\"unfinished\">فعال</translation>\n    </message>\n    <message>\n        <source>Off</source>\n        <translation type=\"unfinished\">خاموش</translation>\n    </message>\n    <message>\n        <source>Confirm</source>\n        <translation type=\"unfinished\">تایید</translation>\n    </message>\n    <message>\n        <source>Server support is required</source>\n        <translation type=\"unfinished\">نیازمند پشتیبانی در سمت سرور</translation>\n    </message>\n    <message>\n        <source>Reality short id. Accept only one value.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>DialogHotkey</name>\n    <message>\n        <source>Hotkey</source>\n        <translation>کلید میانبر</translation>\n    </message>\n    <message>\n        <source>Show routes</source>\n        <translation>مسیرها را نمایش بده</translation>\n    </message>\n    <message>\n        <source>Show groups</source>\n        <translation>نمایش گروه ها</translation>\n    </message>\n    <message>\n        <source>Trigger main window</source>\n        <translation type=\"unfinished\">نمایش پنجره اصلی</translation>\n    </message>\n    <message>\n        <source>System Proxy</source>\n        <translation>پروکسی سیستم</translation>\n    </message>\n</context>\n<context>\n    <name>DialogManageGroups</name>\n    <message>\n        <source>Groups</source>\n        <translation>گروه هاگروه ها</translation>\n    </message>\n    <message>\n        <source>New group</source>\n        <translation>گروه جدید</translation>\n    </message>\n    <message>\n        <source>Update all subscriptions</source>\n        <translation>به روز رسانی تمام اشتراک ها</translation>\n    </message>\n    <message>\n        <source>Confirmation</source>\n        <translation>تاییدیه</translation>\n    </message>\n    <message>\n        <source>Update all subscriptions?</source>\n        <translation>آیا همه اشتراک ها بروزرسانی شوند؟</translation>\n    </message>\n</context>\n<context>\n    <name>DialogManageRoutes</name>\n    <message>\n        <source>Routes</source>\n        <translation>مسیرها</translation>\n    </message>\n    <message>\n        <source>Sniffing Mode</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Disable</source>\n        <translation>غیرفعال کردن</translation>\n    </message>\n    <message>\n        <source>Remote DNS</source>\n        <translation type=\"unfinished\">دی ان اس سمت-سرور</translation>\n    </message>\n    <message>\n        <source>Direct DNS</source>\n        <translation type=\"unfinished\">دی ان اس مستقیم</translation>\n    </message>\n    <message>\n        <source>Enable DNS Routing</source>\n        <translation>فعال کردن مسیریابی DNS</translation>\n    </message>\n    <message>\n        <source>Block</source>\n        <translation type=\"unfinished\">مسدود کردن</translation>\n    </message>\n    <message>\n        <source>Direct</source>\n        <translation>مستقیم</translation>\n    </message>\n    <message>\n        <source>Domain</source>\n        <translation>دامنه</translation>\n    </message>\n    <message>\n        <source>Proxy</source>\n        <translation>پروکسی</translation>\n    </message>\n    <message>\n        <source>IP</source>\n        <translation>آی پی</translation>\n    </message>\n    <message>\n        <source>Preset</source>\n        <translation>پیشفرض</translation>\n    </message>\n    <message>\n        <source>Mange route set</source>\n        <translation type=\"unfinished\">مدیریت مسیرها</translation>\n    </message>\n    <message>\n        <source>Bypass LAN and China</source>\n        <translation type=\"unfinished\">عبور ندادن ترافیک LAN و کشور چین</translation>\n    </message>\n    <message>\n        <source>Global</source>\n        <translation type=\"unfinished\">همگانی</translation>\n    </message>\n    <message>\n        <source>Load</source>\n        <translation type=\"unfinished\">بارگیری</translation>\n    </message>\n    <message>\n        <source>Save</source>\n        <translation>ذخیره کردن</translation>\n    </message>\n    <message>\n        <source>Remove</source>\n        <translation>حذف کردن</translation>\n    </message>\n    <message>\n        <source>Cancel</source>\n        <translation>لغو کردن</translation>\n    </message>\n    <message>\n        <source>Load routing: %1</source>\n        <translation type=\"unfinished\">بارگیری مسیر : 1%</translation>\n    </message>\n    <message>\n        <source>Save routing: %1</source>\n        <translation type=\"unfinished\">ذخیره مسیر : 1%</translation>\n    </message>\n    <message>\n        <source>Remove routing: %1</source>\n        <translation type=\"unfinished\">حذف مسیر : 1%</translation>\n    </message>\n    <message>\n        <source>Default Outbound</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Domain Strategy</source>\n        <translation type=\"unfinished\">استراتژی دامنه</translation>\n    </message>\n    <message>\n        <source>Server Address Strategy</source>\n        <translation type=\"unfinished\">استراتژی آدرس سرور</translation>\n    </message>\n    <message>\n        <source>Sniff result for routing</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Sniff result for destination</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Common</source>\n        <translation type=\"unfinished\">متداول</translation>\n    </message>\n    <message>\n        <source>DNS</source>\n        <translation type=\"unfinished\">دی ان اس</translation>\n    </message>\n    <message>\n        <source>Simple DNS Settings</source>\n        <translation type=\"unfinished\">تنظیمات دی ان اس ساده</translation>\n    </message>\n    <message>\n        <source>Use DNS Object</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>DNS Object Settings</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Simple Route</source>\n        <translation type=\"unfinished\">مسیر ساده</translation>\n    </message>\n    <message>\n        <source>Custom Route</source>\n        <translation type=\"unfinished\">مسیر سفارشی</translation>\n    </message>\n    <message>\n        <source>Custom Route (global)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Note: Other settings are independent for each route set.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Route sets</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Query Strategy</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Document</source>\n        <translation type=\"unfinished\">اسناد</translation>\n    </message>\n    <message>\n        <source>Format</source>\n        <translation type=\"unfinished\">فرمت</translation>\n    </message>\n    <message>\n        <source>This is especially important and it is recommended to use the default value of &quot;localhost&quot;.\nIf the default value does not work, try changing it to &quot;223.5.5.5&quot;.\nFor more information, see the document &quot;Configuration/DNS&quot;.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Final DNS Out</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>DialogVPNSettings</name>\n    <message>\n        <source>Tun Settings</source>\n        <translation>تنظیمات vpn</translation>\n    </message>\n    <message>\n        <source>Hide Console</source>\n        <translation>مخفی کردن کنسول</translation>\n    </message>\n    <message>\n        <source>Tun Enable IPv6</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Bypass CIDR</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Bypass Process Name</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Whether blacklisted or whitelisted, your traffic will be handled by nekobox_core (sing-tun). This is NOT equal to &quot;process mode&quot; of some software.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Whitelist mode</source>\n        <translation type=\"unfinished\">حالت لیست سفید</translation>\n    </message>\n    <message>\n        <source>Proxy CIDR</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Proxy Process Name</source>\n        <translation type=\"unfinished\">نام پروسه پراکسی</translation>\n    </message>\n    <message>\n        <source>Troubleshooting</source>\n        <translation type=\"unfinished\">عیب یابی</translation>\n    </message>\n    <message>\n        <source>If you have trouble starting VPN, you can force reset nekobox_core process here.\n\nIf still not working, see documentation for more information.\nhttps://matsuridayo.github.io/n-configuration/#vpn-tun</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Reset</source>\n        <translation type=\"unfinished\">بازنشانی</translation>\n    </message>\n    <message>\n        <source>Cancel</source>\n        <translation type=\"unfinished\">لغو کردن</translation>\n    </message>\n    <message>\n        <source>Internal Tun</source>\n        <translation type=\"unfinished\">حالت تونل داخلی</translation>\n    </message>\n    <message>\n        <source>Add a tun inbound to the profile startup, instead of using two processes.\nThis needs to be run NekoBox with administrator privileges.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>EditChain</name>\n    <message>\n        <source>Traffic order is from top to bottom</source>\n        <translation type=\"unfinished\">عبور ترافیک از بالا به پایین است</translation>\n    </message>\n    <message>\n        <source>Select Profile</source>\n        <translation>انتخاب کردن پروفایل</translation>\n    </message>\n    <message>\n        <source>Name cannot be empty.</source>\n        <translation type=\"unfinished\">نام نمیتواند خالی باشد</translation>\n    </message>\n</context>\n<context>\n    <name>EditCustom</name>\n    <message>\n        <source>Core</source>\n        <translation>هسته</translation>\n    </message>\n    <message>\n        <source>Json Editor</source>\n        <translation>ویرایشگر Json</translation>\n    </message>\n    <message>\n        <source>Command</source>\n        <translation type=\"unfinished\">فرمان</translation>\n    </message>\n    <message>\n        <source>Config Suffix</source>\n        <translation type=\"unfinished\">پسوند کانفیگ</translation>\n    </message>\n    <message>\n        <source>Outbound JSON, please read the documentation.</source>\n        <translation>JSON خروجی، لطفاً مستندات را بخوانید.</translation>\n    </message>\n    <message>\n        <source>Please pick a core.</source>\n        <translation>لطفا یک هسته انتخاب کنید.</translation>\n    </message>\n    <message>\n        <source>Random if it&apos;s empty or zero.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Preview</source>\n        <translation type=\"unfinished\">پیش نمایش</translation>\n    </message>\n    <message>\n        <source>Preview replace</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Preview config</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Please fill the complete config.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Name cannot be empty.</source>\n        <translation type=\"unfinished\">نام نمیتواند خالی باشد</translation>\n    </message>\n</context>\n<context>\n    <name>EditNaive</name>\n    <message>\n        <source>Protocol</source>\n        <translation>پروتکل</translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>رمزعبور</translation>\n    </message>\n    <message>\n        <source>Extra headers</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>SNI</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Username</source>\n        <translation>نام کاربری</translation>\n    </message>\n    <message>\n        <source>Certificate</source>\n        <translation type=\"unfinished\">گواهی</translation>\n    </message>\n    <message>\n        <source>Insecure concurrency</source>\n        <translation>همزمانی ناامن</translation>\n    </message>\n    <message>\n        <source>Disable logs</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Turn on this option if your connection is lost after a while</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>EditQUIC</name>\n    <message>\n        <source>Certificate</source>\n        <translation type=\"unfinished\">گواهی</translation>\n    </message>\n    <message>\n        <source>Download (Mbps)</source>\n        <translation type=\"unfinished\">دانلود (مگابیت بر ثانیه)</translation>\n    </message>\n    <message>\n        <source>Disable MTU Discovery</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Hop Interval (s)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Allow Insecure</source>\n        <translation type=\"unfinished\">اجازه ارتباطات ناامن داده شود</translation>\n    </message>\n    <message>\n        <source>Hop Port</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Upload (Mbps)</source>\n        <translation type=\"unfinished\">آپلود (مگابیت بر ثانیه)</translation>\n    </message>\n    <message>\n        <source>Obfs Password</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>SNI</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Disable SNI</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Generate UUID</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Heartbeat</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Zero Rtt Handshake</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Congestion Control</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>UDP Relay Mode</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Force use external core</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>EditShadowSocks</name>\n    <message>\n        <source>Plugin Args</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>کلمه عبور</translation>\n    </message>\n    <message>\n        <source>Encryption</source>\n        <translation>رمزگذاری</translation>\n    </message>\n    <message>\n        <source>Plugin</source>\n        <translation>پلاگین</translation>\n    </message>\n    <message>\n        <source>Version of UDP over TCP protocol, server support is required.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Off</source>\n        <translation type=\"unfinished\">خاموش</translation>\n    </message>\n</context>\n<context>\n    <name>EditSocksHttp</name>\n    <message>\n        <source>Version</source>\n        <translation>نسخه</translation>\n    </message>\n    <message>\n        <source>Username</source>\n        <translation>نام کاربری</translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>کلمه عبور</translation>\n    </message>\n</context>\n<context>\n    <name>EditTrojanVLESS</name>\n    <message>\n        <source>Password</source>\n        <translation>کلمه عبور</translation>\n    </message>\n</context>\n<context>\n    <name>EditVMess</name>\n    <message>\n        <source>Alter Id</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Security</source>\n        <translation>امنیت</translation>\n    </message>\n    <message>\n        <source>UUID</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Generate UUID</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>GroupItem</name>\n    <message>\n        <source>Update Subscription</source>\n        <translation>به روز رسانی اشتراک</translation>\n    </message>\n    <message>\n        <source>Edit</source>\n        <translation>ویرایش</translation>\n    </message>\n    <message>\n        <source>Remove</source>\n        <translation>حذف کردن</translation>\n    </message>\n    <message>\n        <source>Basic</source>\n        <translation>پایه</translation>\n    </message>\n    <message>\n        <source>Subscription</source>\n        <translation>اشتراک</translation>\n    </message>\n    <message>\n        <source>Archive</source>\n        <translation>آرشیو</translation>\n    </message>\n    <message>\n        <source>Last update: %1</source>\n        <translation type=\"unfinished\">آخرین آپدیت : %1</translation>\n    </message>\n    <message>\n        <source>Confirmation</source>\n        <translation type=\"unfinished\">تایید</translation>\n    </message>\n    <message>\n        <source>Remove %1?</source>\n        <translation type=\"unfinished\">حذف %1?</translation>\n    </message>\n</context>\n<context>\n    <name>JsonEditor</name>\n    <message>\n        <source>JSON Editor</source>\n        <translation type=\"unfinished\">ویرایشگر JSON</translation>\n    </message>\n    <message>\n        <source>Format JSON</source>\n        <translation type=\"unfinished\">فرمت JSON</translation>\n    </message>\n    <message>\n        <source>Remove All Comments</source>\n        <translation type=\"unfinished\">حذف همه کامنت ها</translation>\n    </message>\n    <message>\n        <source>Json Editor</source>\n        <translation>ویرایشگر Json</translation>\n    </message>\n    <message>\n        <source>Structure Preview</source>\n        <translation type=\"unfinished\">پیش نمایش ساختار</translation>\n    </message>\n    <message>\n        <source>OK</source>\n        <translation type=\"unfinished\">تایید</translation>\n    </message>\n    <message>\n        <source>Json Contains Syntax Errors</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Original Json may contain syntax errors. Json tree is disabled.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>You must correct these errors before continuing.</source>\n        <translation type=\"unfinished\">شما باید این ایرادات را قبل از ادامه دادن اصلاح کنید</translation>\n    </message>\n    <message>\n        <source>Syntax Errors</source>\n        <translation>خطاهای نحوی</translation>\n    </message>\n    <message>\n        <source>Please fix the JSON errors or remove the comments before continue</source>\n        <translation type=\"unfinished\">لطفا قبل از ادامه دادن ایرادات JSON را اصلاح کنید یا کامنت ها را حذف کنید</translation>\n    </message>\n</context>\n<context>\n    <name>MainWindow</name>\n    <message>\n        <source>Program</source>\n        <translation>برنامه</translation>\n    </message>\n    <message>\n        <source>Preferences</source>\n        <translation>تنظیمات</translation>\n    </message>\n    <message>\n        <source>Server</source>\n        <translation>سرور</translation>\n    </message>\n    <message>\n        <source>Ads</source>\n        <translation>تبلیغات</translation>\n    </message>\n    <message>\n        <source>Document</source>\n        <translation>اسناد</translation>\n    </message>\n    <message>\n        <source>Update</source>\n        <translation>بروزرسانی</translation>\n    </message>\n    <message>\n        <source>Tun Mode</source>\n        <translation>حالتvpn</translation>\n    </message>\n    <message>\n        <source>System Proxy</source>\n        <translation>پروکسی سیستمی</translation>\n    </message>\n    <message>\n        <source>Type</source>\n        <translation>نوع</translation>\n    </message>\n    <message>\n        <source>Address</source>\n        <translation>آدرس</translation>\n    </message>\n    <message>\n        <source>Name</source>\n        <translation>اسم</translation>\n    </message>\n    <message>\n        <source>Test Result</source>\n        <translation>نتیجه تست</translation>\n    </message>\n    <message>\n        <source>Traffic</source>\n        <translation>ترافیک</translation>\n    </message>\n    <message>\n        <source>Log</source>\n        <translation>ثبت رویدادها</translation>\n    </message>\n    <message>\n        <source>Connection</source>\n        <translation>اتصال</translation>\n    </message>\n    <message>\n        <source>Status</source>\n        <translation>وضعیت</translation>\n    </message>\n    <message>\n        <source>Outbound</source>\n        <translation>خروجی</translation>\n    </message>\n    <message>\n        <source>Destination</source>\n        <translation>مقصد</translation>\n    </message>\n    <message>\n        <source>Active Server</source>\n        <translation>سرور فعال</translation>\n    </message>\n    <message>\n        <source>Active Routing</source>\n        <translation>مسیریابی فعال</translation>\n    </message>\n    <message>\n        <source>Share</source>\n        <translation>اشتراک گذاری</translation>\n    </message>\n    <message>\n        <source>Current Group</source>\n        <translation>گروه فعلی</translation>\n    </message>\n    <message>\n        <source>Exit</source>\n        <translation>خروج</translation>\n    </message>\n    <message>\n        <source>Basic Settings</source>\n        <translation>تنظیمات پایه</translation>\n    </message>\n    <message>\n        <source>New profile</source>\n        <translation>پروفایل جدید</translation>\n    </message>\n    <message>\n        <source>Groups</source>\n        <translation>گروه ها</translation>\n    </message>\n    <message>\n        <source>Start</source>\n        <translation>آغازکردن</translation>\n    </message>\n    <message>\n        <source>Stop</source>\n        <translation>متوقف کردن</translation>\n    </message>\n    <message>\n        <source>Routing Settings</source>\n        <translation>تنظیمات مسیریابی</translation>\n    </message>\n    <message>\n        <source>Add profile from clipboard</source>\n        <translation>افزودن پروفایل از کلیپ بورد</translation>\n    </message>\n    <message>\n        <source>Delete</source>\n        <translation>از بین بردن</translation>\n    </message>\n    <message>\n        <source>Debug Info</source>\n        <translation>اطلاعات اشکال زدایی</translation>\n    </message>\n    <message>\n        <source>QR Code and link</source>\n        <translation>کد QR و پیوند</translation>\n    </message>\n    <message>\n        <source>Copy Link</source>\n        <translation>لینک را کپی کنید</translation>\n    </message>\n    <message>\n        <source>Clear Test Result</source>\n        <translation>نتایج تست پاک شود</translation>\n    </message>\n    <message>\n        <source>Export %1 config</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Reset Traffic</source>\n        <translation>بازنشانی ترافیک</translation>\n    </message>\n    <message>\n        <source>Scan QR Code</source>\n        <translation>کد QR را اسکن کنید</translation>\n    </message>\n    <message>\n        <source>Enable System Proxy</source>\n        <translation>پروکسی سیستم را فعال کنید</translation>\n    </message>\n    <message>\n        <source>Disable</source>\n        <translation>غیرفعال کردن</translation>\n    </message>\n    <message>\n        <source>Remove Duplicates</source>\n        <translation>موارد تکراری را حذف کردن</translation>\n    </message>\n    <message>\n        <source>fake</source>\n        <translation>جعلی</translation>\n    </message>\n    <message>\n        <source>Move</source>\n        <translation>جابجایی</translation>\n    </message>\n    <message>\n        <source>Start with system</source>\n        <translation>با سیستم شروع شود</translation>\n    </message>\n    <message>\n        <source>Remember last profile</source>\n        <translation>آخرین پروفایل را به خاطر بسپار</translation>\n    </message>\n    <message>\n        <source>Allow other devices to connect</source>\n        <translation>به دستگاه های دیگر اجازه اتصال دهید</translation>\n    </message>\n    <message>\n        <source>Remove Unavailable</source>\n        <translation>غیرقابل دستیابی پاک شود</translation>\n    </message>\n    <message>\n        <source>Full Test</source>\n        <translation>تست کامل</translation>\n    </message>\n    <message>\n        <source>Hotkey Settings</source>\n        <translation>تنظیمات کلید میانبر</translation>\n    </message>\n    <message>\n        <source>Select All</source>\n        <translation>انتخاب همه</translation>\n    </message>\n    <message>\n        <source>Copy links of selected (Neko Links)</source>\n        <translation>کپی لینک های انتخاب شده (پیوندهای Neko)</translation>\n    </message>\n    <message>\n        <source>Copy links of selected</source>\n        <translation>لینک های انتخاب شده را کپی کنید</translation>\n    </message>\n    <message>\n        <source>Enable Tun</source>\n        <translation>فعال کردن tun</translation>\n    </message>\n    <message>\n        <source>Clone</source>\n        <translation>همزادسازی</translation>\n    </message>\n    <message>\n        <source>Update subscription</source>\n        <translation>اشتراک را به روز کنید</translation>\n    </message>\n    <message>\n        <source>Resolve domain</source>\n        <translation>دامنه را حل کنید</translation>\n    </message>\n    <message>\n        <source>Tun Settings</source>\n        <translation>تنظیمات vpn</translation>\n    </message>\n    <message>\n        <source>Restart Program</source>\n        <translation>اجرا دوباره برنامه</translation>\n    </message>\n    <message>\n        <source>Open Config Folder</source>\n        <translation>پوشه Config باز شود</translation>\n    </message>\n    <message>\n        <source>Load routing and apply: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Error</source>\n        <translation>خطا</translation>\n    </message>\n    <message>\n        <source>Tun Settings changed</source>\n        <translation>تنظیمات Tun تغییر کرد</translation>\n    </message>\n    <message>\n        <source>Restart Tun to take effect.</source>\n        <translation>Tun را مجدداً راه اندازی کنید تا اعمال شود.</translation>\n    </message>\n    <message>\n        <source>Confirmation</source>\n        <translation>تائیدیه</translation>\n    </message>\n    <message>\n        <source>Settings changed, restart proxy?</source>\n        <translation>تنظیمات تغییر کرد، پراکسی راه اندازی مجدد شود؟</translation>\n    </message>\n    <message>\n        <source>Imported %1 profile(s)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Current server is incompatible with Tun. Please stop the server first, enable Tun Mode, and then restart.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Not Running</source>\n        <translation>در حال اجرا نیست</translation>\n    </message>\n    <message>\n        <source>Select</source>\n        <translation>انتخاب</translation>\n    </message>\n    <message>\n        <source>Clone %1 item(s)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Move %1 item(s)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Remove %1 item(s) ?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Copied %1 item(s)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Config copied</source>\n        <translation>کانفیگ کپی شد</translation>\n    </message>\n    <message>\n        <source>QR Code not found</source>\n        <translation>کد QR یافت نشد</translation>\n    </message>\n    <message>\n        <source>Resolving domain to IP, if support.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Set ignore keyword</source>\n        <translation>کلیدواژه نادیده گرفتن را تنظیم کنید</translation>\n    </message>\n    <message>\n        <source>Set the following keywords to ignore?\nSplit by line.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Save as route</source>\n        <translation>ذخیره به عنوان مسیر</translation>\n    </message>\n    <message>\n        <source>Edit</source>\n        <translation>ویرایش کردن</translation>\n    </message>\n    <message>\n        <source>Save &quot;%1&quot; as a routing rule?</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Clear</source>\n        <translation>پاک کردن</translation>\n    </message>\n    <message>\n        <source>Start: %1\nEnd: %2</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Failed to stop Tun process</source>\n        <translation>فرآیند Tun متوقف نشد</translation>\n    </message>\n    <message>\n        <source>[%1] test error: %2</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Testing</source>\n        <translation>آزمایش کردن</translation>\n    </message>\n    <message>\n        <source>Unavailable</source>\n        <translation>غیرقایل دسترسی</translation>\n    </message>\n    <message>\n        <source>Starting profile %1</source>\n        <translation>اغاز پروفایل %1</translation>\n    </message>\n    <message>\n        <source>Stopping profile %1</source>\n        <translation>متوقف کردن پروفایل %1</translation>\n    </message>\n    <message>\n        <source>Current Select</source>\n        <translation type=\"unfinished\">انتخاب فعلی</translation>\n    </message>\n    <message>\n        <source>Show Window</source>\n        <translation>نمایش پنجره برنامه</translation>\n    </message>\n    <message>\n        <source>Settings changed</source>\n        <translation type=\"unfinished\">تنظیمات تغییر کرد</translation>\n    </message>\n    <message>\n        <source>Please run NekoBox as admin</source>\n        <translation type=\"unfinished\">لطفا Nekobox را با مجوز ادمین اجرا کنید</translation>\n    </message>\n    <message>\n        <source>Restart Proxy</source>\n        <translation type=\"unfinished\">راه اندازی مجدد پروکسی</translation>\n    </message>\n    <message>\n        <source>If there is no response for a long time, it is recommended to restart the software.</source>\n        <translation type=\"unfinished\">اگر برای مدت زمان طولانی هیچ پاسخی دریافت نشد ، پیشنهاد میشود که نرم افزار را دوباره اجرا کنید</translation>\n    </message>\n    <message>\n        <source>Failed to start profile %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Failed to stop, please restart the program.</source>\n        <translation type=\"unfinished\">توقف ناموفق بود ، لطفا برنامه را دوباره اجرا کنید</translation>\n    </message>\n    <message>\n        <source>Select mode, double-click or press Enter to select a profile, press ESC to exit.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Latency</source>\n        <translation type=\"unfinished\">تاخیر</translation>\n    </message>\n    <message>\n        <source>UDP latency</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Download speed</source>\n        <translation type=\"unfinished\">سرعت دانلود</translation>\n    </message>\n    <message>\n        <source>In and Out IP</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Test Options</source>\n        <translation type=\"unfinished\">گزینه های تست</translation>\n    </message>\n    <message>\n        <source>Restart the program to take effect.</source>\n        <translation type=\"unfinished\">برای مشاهده نتیجه برنامه را دوباره راه اندازی کنید</translation>\n    </message>\n    <message>\n        <source>Stop Testing</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>URL Test</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>ProxyItem</name>\n    <message>\n        <source>Confirmation</source>\n        <translation>تائیدیه</translation>\n    </message>\n    <message>\n        <source>Remove %1?</source>\n        <translation type=\"unfinished\">حذف %1?</translation>\n    </message>\n</context>\n<context>\n    <name>QGuiApplication</name>\n    <message>\n        <source>QT_LAYOUT_DIRECTION</source>\n        <translation>RTL</translation>\n    </message>\n</context>\n<context>\n    <name>QObject</name>\n    <message>\n        <source>Used: %1 Remain: %2 Expire: %3</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Select</source>\n        <translation>انتخاب کردن</translation>\n    </message>\n    <message>\n        <source>Update</source>\n        <translation>بروزرسانی</translation>\n    </message>\n    <message>\n        <source>No update</source>\n        <translation>بدون بروزرسانی جدید</translation>\n    </message>\n    <message>\n        <source>Update found: %1\nRelease note:\n%2</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Open in browser</source>\n        <translation>در مرور گر باز شود</translation>\n    </message>\n    <message>\n        <source>Close</source>\n        <translation>بستن</translation>\n    </message>\n    <message>\n        <source>Update is ready, restart to install?</source>\n        <translation>به روز رسانی آماده است، برای نصب مجدد راه اندازی شود؟</translation>\n    </message>\n    <message>\n        <source>As link</source>\n        <translation>به عنوان لینک</translation>\n    </message>\n    <message>\n        <source>url detected</source>\n        <translation type=\"unfinished\">آدرس شناسایی شد</translation>\n    </message>\n    <message>\n        <source>%1\nHow to update?</source>\n        <translation type=\"unfinished\">چگونه بروزرسانی کنم ؟</translation>\n    </message>\n    <message>\n        <source>Requesting subscription: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Requesting subscription %1 error: %2</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Clearing servers...</source>\n        <translation type=\"unfinished\">پاک کردن سرورها</translation>\n    </message>\n    <message>\n        <source>Added %1 profiles:\n%2\nDeleted %3 Profiles:\n%4</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Nothing</source>\n        <translation type=\"unfinished\">خالی</translation>\n    </message>\n    <message>\n        <source>Change of %1:</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Chain Proxy</source>\n        <translation>پروکسی زنجیره ای</translation>\n    </message>\n    <message>\n        <source>Core not found: %1</source>\n        <translation type=\"unfinished\">هسته برنامه یافت نشد : %1</translation>\n    </message>\n    <message>\n        <source>Proxy: %1\nDirect: %2</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Unavailable</source>\n        <translation>غیرقابل دسترس</translation>\n    </message>\n    <message>\n        <source>Request with proxy but no profile started.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Subscription request fininshed: %1</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Core exited, restarting.</source>\n        <translation type=\"unfinished\">هسته برنامه متوقف شد ، در حال راه اندازی مجدد</translation>\n    </message>\n    <message>\n        <source>Core exits too frequently, stop automatic restart this profile.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>As Subscription (create new group)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>As Subscription (add to this group)</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Default</source>\n        <translation type=\"unfinished\">پیش فرض</translation>\n    </message>\n    <message>\n        <source>The last speed test did not exit completely, please wait. If it persists, please restart the program.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>Qv2ray::ui::widgets::AutoCompleteTextEdit</name>\n    <message>\n        <source>You can not input space characters here.</source>\n        <translation>شما نمی توانید کاراکتر فضای خالی در اینجا استفاده کنید.</translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "translations/ru_RU.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"ru_RU\">\n<context>\n    <name>DialogBasicSettings</name>\n    <message>\n        <source>Basic Settings</source>\n        <translation>Основные настройки</translation>\n    </message>\n    <message>\n        <source>Common</source>\n        <translation>Общие</translation>\n    </message>\n    <message>\n        <source>Listen Address</source>\n        <translation>Адрес входящих подключений</translation>\n    </message>\n    <message>\n        <source>Custom Inbound</source>\n        <translation>Кастомный inbound</translation>\n    </message>\n    <message>\n        <source>Edit</source>\n        <translation>Изменить</translation>\n    </message>\n    <message>\n        <source>Enable</source>\n        <translation>Вкл</translation>\n    </message>\n    <message>\n        <source>Latency Test URL</source>\n        <translation>URL теста задержки</translation>\n    </message>\n    <message>\n        <source>Concurrent</source>\n        <translation>Параллельно</translation>\n    </message>\n    <message>\n        <source>Download Test URL</source>\n        <translation>URL теста загрузки</translation>\n    </message>\n    <message>\n        <source>Include Pre-release when checking update</source>\n        <translation>Проверять пре-релизы при обновлениях</translation>\n    </message>\n    <message>\n        <source>System proxy format</source>\n        <translation>Формат строки системного прокси</translation>\n    </message>\n    <message>\n        <source>Style</source>\n        <translation>Стиль</translation>\n    </message>\n    <message>\n        <source>Theme</source>\n        <translation>Тема</translation>\n    </message>\n    <message>\n        <source>System</source>\n        <translation>Системная</translation>\n    </message>\n    <message>\n        <source>Set custom icon</source>\n        <translation>Задать иконку</translation>\n    </message>\n    <message>\n        <source>Statistics refresh rate</source>\n        <translation>Период обновления статистики</translation>\n    </message>\n    <message>\n        <source>Off</source>\n        <translation>Выкл</translation>\n    </message>\n    <message>\n        <source>Connection statistics</source>\n        <translation>Статистика подключений</translation>\n    </message>\n    <message>\n        <source>Hide dashboard at startup</source>\n        <translation>Спрятать окно при старте</translation>\n    </message>\n    <message>\n        <source>Max log lines</source>\n        <translation>Макс. строк в логе</translation>\n    </message>\n    <message>\n        <source>Subscription</source>\n        <translation>Подписка</translation>\n    </message>\n    <message>\n        <source>User Agent</source>\n        <translation>User Agent</translation>\n    </message>\n    <message>\n        <source>Use proxy when updating subscription</source>\n        <translation>Использовать прокси при обновлении подписок</translation>\n    </message>\n    <message>\n        <source>Ignore TLS errors when updating subscription</source>\n        <translation>Игнорировать ошибки TLS при обновлении подписок</translation>\n    </message>\n    <message>\n        <source>Clear servers before updating subscription</source>\n        <translation>Очищать список серверов при обновлении подписок</translation>\n    </message>\n    <message>\n        <source>Core</source>\n        <translation>Ядро</translation>\n    </message>\n    <message>\n        <source>Select</source>\n        <translation>Выбрать</translation>\n    </message>\n    <message>\n        <source>Multiplex (mux)</source>\n        <translation>Мультиплексирование (mux)</translation>\n    </message>\n    <message>\n        <source>concurrency</source>\n        <translation>многопоточность</translation>\n    </message>\n    <message>\n        <source>Default On</source>\n        <translation>Вкл. по умолчанию</translation>\n    </message>\n    <message>\n        <source>Core Options</source>\n        <translation>Параметры ядра</translation>\n    </message>\n    <message>\n        <source>Extra Core</source>\n        <translation>Дополнительные ядра</translation>\n    </message>\n    <message>\n        <source>Add</source>\n        <translation>Добавить</translation>\n    </message>\n    <message>\n        <source>Delete</source>\n        <translation>Удалить</translation>\n    </message>\n    <message>\n        <source>Security</source>\n        <translation>Безопасность</translation>\n    </message>\n    <message>\n        <source>Skip TLS certificate authentication by default (allowInsecure)</source>\n        <translation>Не проверять подлинность TLS сертификатов по умолчанию</translation>\n    </message>\n    <message>\n        <source>Default uTLS Fingerprint</source>\n        <translation>uTLS fingerprint по умолчанию</translation>\n    </message>\n    <message>\n        <source>Advanced system proxy settings. Please select a format.</source>\n        <translation>Дополнительные настройки системного прокси. Пожалуйста, выберите формат.</translation>\n    </message>\n    <message>\n        <source>Please input the core name.</source>\n        <translation>Введите имя ядра.</translation>\n    </message>\n    <message>\n        <source>Please select the core name.</source>\n        <translation>Выберите имя ядра.</translation>\n    </message>\n    <message>\n        <source>Please select a PNG file.</source>\n        <translation>Выберите PNG файл</translation>\n    </message>\n    <message>\n        <source>Reset</source>\n        <translation>Сброс</translation>\n    </message>\n    <message>\n        <source>Cancel</source>\n        <translation>Отмена</translation>\n    </message>\n    <message>\n        <source>Please select a valid square image.</source>\n        <translation>Пожалуйста, выберите корректное квадратное изображение.</translation>\n    </message>\n    <message>\n        <source>Inbound Auth</source>\n        <translation>Аутентификация inbound</translation>\n    </message>\n    <message>\n        <source>Username</source>\n        <translation>Имя пользователя</translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>Пароль</translation>\n    </message>\n    <message>\n        <source>Override underlying DNS</source>\n        <translation>Переопределить нижестоящий DNS</translation>\n    </message>\n    <message>\n        <source>Timeout (s)</source>\n        <translation>Таймаут (с)</translation>\n    </message>\n    <message>\n        <source>Automatic update</source>\n        <translation>Автоматическое обновление</translation>\n    </message>\n    <message>\n        <source>Interval (minute, invalid if less than 30)</source>\n        <translation>Интервал (в минутах, значение считается неправильным, если меньше 30)</translation>\n    </message>\n    <message>\n        <source>Share VMess Link with v2rayN Format</source>\n        <translation>Поделиться ссылкой VMess в формате v2rayN</translation>\n    </message>\n    <message>\n        <source>Old Share Link Format</source>\n        <translation>Поделиться ссылкой в старом формате</translation>\n    </message>\n    <message>\n        <source>Mixed (SOCKS+HTTP) Listen Port</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>DialogEditGroup</name>\n    <message>\n        <source>Edit Group</source>\n        <translation>Изменить группу</translation>\n    </message>\n    <message>\n        <source>Name</source>\n        <translation>Имя</translation>\n    </message>\n    <message>\n        <source>Manually column width</source>\n        <translation>Уст. ширину колонок</translation>\n    </message>\n    <message>\n        <source>Archive</source>\n        <translation>Архив</translation>\n    </message>\n    <message>\n        <source>Type</source>\n        <translation>Тип</translation>\n    </message>\n    <message>\n        <source>Basic</source>\n        <translation>Простая</translation>\n    </message>\n    <message>\n        <source>Subscription</source>\n        <translation>Подписка (subscription)</translation>\n    </message>\n    <message>\n        <source>Front Proxy</source>\n        <translation>Фронт-прокси</translation>\n    </message>\n    <message>\n        <source>Clear</source>\n        <translation>Сбросить</translation>\n    </message>\n    <message>\n        <source>URL</source>\n        <translation>URL</translation>\n    </message>\n    <message>\n        <source>Copy profile share links</source>\n        <translation>Скопировать ссылки на профиль</translation>\n    </message>\n    <message>\n        <source>Copy profile share links (Neko Links)</source>\n        <translation>Скопировать ссылки на профиль (Neko links)</translation>\n    </message>\n    <message>\n        <source>Copied</source>\n        <translation>Скопировано</translation>\n    </message>\n    <message>\n        <source>Warning</source>\n        <translation>Предупреждение</translation>\n    </message>\n    <message>\n        <source>Please input URL</source>\n        <translation>Пожалуйста, введите URL</translation>\n    </message>\n    <message>\n        <source>None</source>\n        <translation>Нет</translation>\n    </message>\n    <message>\n        <source>Skip automatic update</source>\n        <translation>Пропустить автоматическое обновление</translation>\n    </message>\n    <message>\n        <source>Common</source>\n        <translation>Общие</translation>\n    </message>\n    <message>\n        <source>Share</source>\n        <translation>Поделиться</translation>\n    </message>\n</context>\n<context>\n    <name>DialogEditProfile</name>\n    <message>\n        <source>Edit</source>\n        <translation>Редактировать</translation>\n    </message>\n    <message>\n        <source>Common</source>\n        <translation>Общие</translation>\n    </message>\n    <message>\n        <source>Type</source>\n        <translation>Тип</translation>\n    </message>\n    <message>\n        <source>Port</source>\n        <translation>Порт</translation>\n    </message>\n    <message>\n        <source>Address</source>\n        <translation>Адрес</translation>\n    </message>\n    <message>\n        <source>Name</source>\n        <translation>Имя</translation>\n    </message>\n    <message>\n        <source>Custom Outbound Settings</source>\n        <translation>Доп. настройки outbound</translation>\n    </message>\n    <message>\n        <source>Custom Config Settings</source>\n        <translation>Доп. настройки конфига</translation>\n    </message>\n    <message>\n        <source>Apply settings to this group</source>\n        <translation>Применить настройки к этой группе</translation>\n    </message>\n    <message>\n        <source>Settings</source>\n        <translation>Настройки</translation>\n    </message>\n    <message>\n        <source>The underlying transport method. It must be consistent with the server, otherwise, the connection cannot be established.</source>\n        <translation>Нижележащий транспорт. Должен соответствовать конфигурации сервера, иначе подключение будет невозможно.</translation>\n    </message>\n    <message>\n        <source>Network</source>\n        <translation>Транспорт</translation>\n    </message>\n    <message>\n        <source>Transport Layer Security. It must be consistent with the server, otherwise, the connection cannot be established.</source>\n        <translation>TLS. Должно совпадать с параметрами сервера, иначе подключение будет невозможно.</translation>\n    </message>\n    <message>\n        <source>Security</source>\n        <translation>Безопасность</translation>\n    </message>\n    <message>\n        <source>UDP FullCone Packet encoding for implementing features such as UDP FullCone. Server support is required, if the wrong selection is made, the connection cannot be made. Please leave it blank.</source>\n        <translation>UDP FullCone кодирование пакетов для реализации функционала типа UDP FullCone. Необходима поддержка со стороны сервера, при неправильном выборе подключение не будет работать. Оставьте пустым, если вы не знаете что и зачем это.</translation>\n    </message>\n    <message>\n        <source>Packet Encoding</source>\n        <translation>Кодирование пакетов</translation>\n    </message>\n    <message>\n        <source>Server support is required</source>\n        <translation>Необходима поддержка со стороны сервера</translation>\n    </message>\n    <message>\n        <source>Multiplex</source>\n        <translation>Мультиплексирование</translation>\n    </message>\n    <message>\n        <source>Keep Default</source>\n        <translation>По умолчанию</translation>\n    </message>\n    <message>\n        <source>On</source>\n        <translation>Вкл</translation>\n    </message>\n    <message>\n        <source>Off</source>\n        <translation>Выкл</translation>\n    </message>\n    <message>\n        <source>Network Settings (%1)</source>\n        <translation>Настройки транспорта (%1)</translation>\n    </message>\n    <message>\n        <source>TLS Security Settings</source>\n        <translation>Настройки TLS</translation>\n    </message>\n    <message>\n        <source>When enabled, V2Ray will not check the validity of the TLS certificate provided by the remote host (the security is equivalent to plaintext)</source>\n        <translation>Если вкл., то  клиент не будет проверять валидность TLS-сертификата, предоставленного сервером</translation>\n    </message>\n    <message>\n        <source>Allow insecure</source>\n        <translation>Разрешить небезопасн.</translation>\n    </message>\n    <message>\n        <source>Certificate</source>\n        <translation>Сертификат</translation>\n    </message>\n    <message>\n        <source>Server name indication, clear text.</source>\n        <translation>SNI (идентификатор сервера, передается в открытом виде).</translation>\n    </message>\n    <message>\n        <source>Application layer protocol negotiation, clear text. Please separate them with commas.</source>\n        <translation>ALPN, идентификатор протокола приложения, передается открытым текстом. Используйте запятую в качестве разделителя.</translation>\n    </message>\n    <message>\n        <source>TLS Camouflage Settings</source>\n        <translation>Настройки маскировки TLS</translation>\n    </message>\n    <message>\n        <source>Reality public key. If not empty, turn TLS into REALITY.</source>\n        <translation>Публичный ключ Reality. Если задано значение, то будет использован протокол Reality для TLS.</translation>\n    </message>\n    <message>\n        <source>Reality short id. Accept only one value.</source>\n        <translation>Короткий ID для Reality. Можно задать только одно значение.</translation>\n    </message>\n    <message>\n        <source>Custom (%1 outbound)</source>\n        <translation>Кастомный (%1 outbound)</translation>\n    </message>\n    <message>\n        <source>Custom (%1 config)</source>\n        <translation>Кастомный (%1 конфиг)</translation>\n    </message>\n    <message>\n        <source>Custom (Extra Core)</source>\n        <translation>Кастомный (доп. ядро)</translation>\n    </message>\n    <message>\n        <source>Not set</source>\n        <translation>Не задано</translation>\n    </message>\n    <message>\n        <source>Already set</source>\n        <translation>Уже задано</translation>\n    </message>\n    <message>\n        <source>Confirm</source>\n        <translation>Подтвердить</translation>\n    </message>\n</context>\n<context>\n    <name>DialogHotkey</name>\n    <message>\n        <source>Hotkey</source>\n        <translation>Горячие клавиши</translation>\n    </message>\n    <message>\n        <source>Show routes</source>\n        <translation>Показать маршруты</translation>\n    </message>\n    <message>\n        <source>Show groups</source>\n        <translation>Показать группы</translation>\n    </message>\n    <message>\n        <source>Trigger main window</source>\n        <translation>Показать главное окно</translation>\n    </message>\n    <message>\n        <source>System Proxy</source>\n        <translation>Режим системного прокси</translation>\n    </message>\n</context>\n<context>\n    <name>DialogManageGroups</name>\n    <message>\n        <source>Groups</source>\n        <translation>Группы</translation>\n    </message>\n    <message>\n        <source>New group</source>\n        <translation>Новая группа</translation>\n    </message>\n    <message>\n        <source>Update all subscriptions</source>\n        <translation>Обновить все подписки</translation>\n    </message>\n    <message>\n        <source>Confirmation</source>\n        <translation>Подтвердить</translation>\n    </message>\n    <message>\n        <source>Update all subscriptions?</source>\n        <translation>Обновить все подписки?</translation>\n    </message>\n</context>\n<context>\n    <name>DialogManageRoutes</name>\n    <message>\n        <source>Routes</source>\n        <translation>Маршруты</translation>\n    </message>\n    <message>\n        <source>Common</source>\n        <translation>Общие</translation>\n    </message>\n    <message>\n        <source>Route sets</source>\n        <translation>Набор маршрутов</translation>\n    </message>\n    <message>\n        <source>Mange route set</source>\n        <translation>Изменить набор маршрутов</translation>\n    </message>\n    <message>\n        <source>Custom Route (global)</source>\n        <translation>Кастомные маршруты (global)</translation>\n    </message>\n    <message>\n        <source>Note: Other settings are independent for each route set.</source>\n        <translation>Остальные настройки будут индивидуальны\nдля каждого набора маршрутов.</translation>\n    </message>\n    <message>\n        <source>Domain Strategy</source>\n        <translation>Стратегия доменов</translation>\n    </message>\n    <message>\n        <source>Disable</source>\n        <translation>Выкл</translation>\n    </message>\n    <message>\n        <source>Sniff result for routing</source>\n        <translation>Подслушивать для маршрутизации</translation>\n    </message>\n    <message>\n        <source>Sniff result for destination</source>\n        <translation>Подслушивать для точки назначения</translation>\n    </message>\n    <message>\n        <source>Sniffing Mode</source>\n        <translation>Режим подслушивания</translation>\n    </message>\n    <message>\n        <source>Server Address Strategy</source>\n        <translation>Стратегия выбора адреса сервера</translation>\n    </message>\n    <message>\n        <source>DNS</source>\n        <translation>DNS</translation>\n    </message>\n    <message>\n        <source>Simple DNS Settings</source>\n        <translation>Базовые настройки DNS</translation>\n    </message>\n    <message>\n        <source>Direct DNS</source>\n        <translation>DNS для &quot;прямых&quot; запросов</translation>\n    </message>\n    <message>\n        <source>Query Strategy</source>\n        <translation>Стратегия запросов</translation>\n    </message>\n    <message>\n        <source>Remote DNS</source>\n        <translation>Удаленный (remote) DNS</translation>\n    </message>\n    <message>\n        <source>Enable DNS Routing</source>\n        <translation>Вкл. DNS-маршрутизацию</translation>\n    </message>\n    <message>\n        <source>DNS Object Settings</source>\n        <translation>Специальные настройки DNS</translation>\n    </message>\n    <message>\n        <source>Use DNS Object</source>\n        <translation>Использовать DNS-объект</translation>\n    </message>\n    <message>\n        <source>Format</source>\n        <translation>Форматировать</translation>\n    </message>\n    <message>\n        <source>Document</source>\n        <translation>Помощь</translation>\n    </message>\n    <message>\n        <source>Simple Route</source>\n        <translation>Базовые маршруты</translation>\n    </message>\n    <message>\n        <source>Block</source>\n        <translation>Блок</translation>\n    </message>\n    <message>\n        <source>Direct</source>\n        <translation>Напрямую</translation>\n    </message>\n    <message>\n        <source>Domain</source>\n        <translation>Домен</translation>\n    </message>\n    <message>\n        <source>Proxy</source>\n        <translation>Прокси</translation>\n    </message>\n    <message>\n        <source>IP</source>\n        <translation>IP</translation>\n    </message>\n    <message>\n        <source>Preset</source>\n        <translation>Пресет</translation>\n    </message>\n    <message>\n        <source>Custom Route</source>\n        <translation>Кастомные маршруты</translation>\n    </message>\n    <message>\n        <source>Default Outbound</source>\n        <translation>Outbound по-умолчанию</translation>\n    </message>\n    <message>\n        <source>Bypass LAN and China</source>\n        <translation>Пропускать LAN и китайские ресурсы</translation>\n    </message>\n    <message>\n        <source>Global</source>\n        <translation>Глобально</translation>\n    </message>\n    <message>\n        <source>Load</source>\n        <translation>Загрузить</translation>\n    </message>\n    <message>\n        <source>Save</source>\n        <translation>Сохранить</translation>\n    </message>\n    <message>\n        <source>Remove</source>\n        <translation>Удалить</translation>\n    </message>\n    <message>\n        <source>Cancel</source>\n        <translation>Отменить</translation>\n    </message>\n    <message>\n        <source>Load routing: %1</source>\n        <translation>Загрузить машруты: %1</translation>\n    </message>\n    <message>\n        <source>Save routing: %1</source>\n        <translation>Сохранить маршруты: %1</translation>\n    </message>\n    <message>\n        <source>Remove routing: %1</source>\n        <translation>Удалить маршруты: %1</translation>\n    </message>\n    <message>\n        <source>This is especially important and it is recommended to use the default value of &quot;localhost&quot;.\nIf the default value does not work, try changing it to &quot;223.5.5.5&quot;.\nFor more information, see the document &quot;Configuration/DNS&quot;.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Final DNS Out</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>DialogVPNSettings</name>\n    <message>\n        <source>Tun Settings</source>\n        <translation>Настройки Tun</translation>\n    </message>\n    <message>\n        <source>Tun Enable IPv6</source>\n        <translation>Вкл. IPv6 в Tun</translation>\n    </message>\n    <message>\n        <source>Add a tun inbound to the profile startup, instead of using two processes.\nThis needs to be run NekoBox with administrator privileges.</source>\n        <translation>Добавить inbound c Tun в конфигурацию профиля вместо того, чтобы использовать два отдельных процесса.\nДля этого необходимо запускать NekoBox c правами администратора.</translation>\n    </message>\n    <message>\n        <source>Internal Tun</source>\n        <translation>Встроен. Tun</translation>\n    </message>\n    <message>\n        <source>Hide Console</source>\n        <translation>Скрывать окно</translation>\n    </message>\n    <message>\n        <source>Bypass CIDR</source>\n        <translation>Пропускать CIDR</translation>\n    </message>\n    <message>\n        <source>Bypass Process Name</source>\n        <translation>Пропускать процессы</translation>\n    </message>\n    <message>\n        <source>Whether blacklisted or whitelisted, your traffic will be handled by nekobox_core (sing-tun). This is NOT equal to &quot;process mode&quot; of some software.</source>\n        <translation>При использовании белого или черного списка, ваш трафик будет обработан ядром nekobox (sign-tun). Это НЕ эквивалент &quot;process mode&quot; как в некотором ПО.</translation>\n    </message>\n    <message>\n        <source>Whitelist mode</source>\n        <translation>Режим белого списка</translation>\n    </message>\n    <message>\n        <source>Troubleshooting</source>\n        <translation>Исправление проблем</translation>\n    </message>\n    <message>\n        <source>Proxy CIDR</source>\n        <translation>Проксировать CIDR</translation>\n    </message>\n    <message>\n        <source>Proxy Process Name</source>\n        <translation>Проксировать процессы</translation>\n    </message>\n    <message>\n        <source>If you have trouble starting VPN, you can force reset nekobox_core process here.\n\nIf still not working, see documentation for more information.\nhttps://matsuridayo.github.io/n-configuration/#vpn-tun</source>\n        <translation>Если у вас проблемы с запуском VPN, можно принудительно перезапустить процесс nekobox-core.\n\nЕсли ничего по-прежнему не работает, ознакомьтесь с документацией:\nhttps://matsuridayo.github.io/n-configuration/#vpn-tun</translation>\n    </message>\n    <message>\n        <source>Reset</source>\n        <translation>Перезапустить</translation>\n    </message>\n    <message>\n        <source>Cancel</source>\n        <translation>Отмена</translation>\n    </message>\n</context>\n<context>\n    <name>EditChain</name>\n    <message>\n        <source>Traffic order is from top to bottom</source>\n        <translation>Порядок трафика сверху вниз</translation>\n    </message>\n    <message>\n        <source>Select Profile</source>\n        <translation>Выбор профиля</translation>\n    </message>\n    <message>\n        <source>Name cannot be empty.</source>\n        <translation>Имя не может быть пустым.</translation>\n    </message>\n</context>\n<context>\n    <name>EditCustom</name>\n    <message>\n        <source>Core</source>\n        <translation>Ядро</translation>\n    </message>\n    <message>\n        <source>Json Editor</source>\n        <translation>Редактор JSON</translation>\n    </message>\n    <message>\n        <source>Command</source>\n        <translation>Команда</translation>\n    </message>\n    <message>\n        <source>Config Suffix</source>\n        <translation>Суффикс конфига</translation>\n    </message>\n    <message>\n        <source>Random if it&apos;s empty or zero.</source>\n        <translation>Если пусто или 0, то будет выбран случайным образом.</translation>\n    </message>\n    <message>\n        <source>Preview</source>\n        <translation>Предпросмотр</translation>\n    </message>\n    <message>\n        <source>Outbound JSON, please read the documentation.</source>\n        <translation>Outbound JSON, читайте документацию.</translation>\n    </message>\n    <message>\n        <source>Please fill the complete config.</source>\n        <translation>Пожалуйста, введите полную конфигурацию.</translation>\n    </message>\n    <message>\n        <source>Preview replace</source>\n        <translation>Предпросмотр замен</translation>\n    </message>\n    <message>\n        <source>Preview config</source>\n        <translation>Предпросмотр конфигурации</translation>\n    </message>\n    <message>\n        <source>Name cannot be empty.</source>\n        <translation>Имя не может быть пустым.</translation>\n    </message>\n    <message>\n        <source>Please pick a core.</source>\n        <translation>Пожалуйста, выберите ядро.</translation>\n    </message>\n</context>\n<context>\n    <name>EditNaive</name>\n    <message>\n        <source>Protocol</source>\n        <translation>Протокол</translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>Пароль</translation>\n    </message>\n    <message>\n        <source>Extra headers</source>\n        <translation>Дополнительные заголовки</translation>\n    </message>\n    <message>\n        <source>SNI</source>\n        <translation>SNI</translation>\n    </message>\n    <message>\n        <source>Username</source>\n        <translation>Имя пользователя</translation>\n    </message>\n    <message>\n        <source>Certificate</source>\n        <translation>Сертификат</translation>\n    </message>\n    <message>\n        <source>Insecure concurrency</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Disable logs</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>Turn on this option if your connection is lost after a while</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>EditQUIC</name>\n    <message>\n        <source>Download (Mbps)</source>\n        <translation>Скорость приема (Mbps)</translation>\n    </message>\n    <message>\n        <source>Disable MTU Discovery</source>\n        <translation>Выкл. MTU discovery</translation>\n    </message>\n    <message>\n        <source>Hop Interval (s)</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Certificate</source>\n        <translation>Сертификат</translation>\n    </message>\n    <message>\n        <source>Allow Insecure</source>\n        <translation>Разрешить небезопасные</translation>\n    </message>\n    <message>\n        <source>Hop Port</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Upload (Mbps)</source>\n        <translation>Скорость отдачи (Mbps)</translation>\n    </message>\n    <message>\n        <source>Obfs Password</source>\n        <translation>Пароль для обфускации</translation>\n    </message>\n    <message>\n        <source>SNI</source>\n        <translation>SNI</translation>\n    </message>\n    <message>\n        <source>Disable SNI</source>\n        <translation>Отключить SNI</translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>Пароль</translation>\n    </message>\n    <message>\n        <source>Generate UUID</source>\n        <translation>Сгенерировать UUID</translation>\n    </message>\n    <message>\n        <source>Heartbeat</source>\n        <translation>Сердцебиение (Hearbeat)</translation>\n    </message>\n    <message>\n        <source>Zero Rtt Handshake</source>\n        <translation>Без рукопожатия Rtt</translation>\n    </message>\n    <message>\n        <source>Congestion Control</source>\n        <translation>Управление перегрузкой</translation>\n    </message>\n    <message>\n        <source>UDP Relay Mode</source>\n        <translation>Режим UDP Relay</translation>\n    </message>\n    <message>\n        <source>Force use external core</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>EditShadowSocks</name>\n    <message>\n        <source>Encryption</source>\n        <translation>Шифрование</translation>\n    </message>\n    <message>\n        <source>Plugin</source>\n        <translation>Плагин</translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>Пароль</translation>\n    </message>\n    <message>\n        <source>Plugin Args</source>\n        <translation>Аргументы</translation>\n    </message>\n    <message>\n        <source>Version of UDP over TCP protocol, server support is required.</source>\n        <translation>Версия протокола &quot;UDP over TCP&quot;, требуется поддержка со стороны сервера.</translation>\n    </message>\n    <message>\n        <source>Off</source>\n        <translation>Выкл</translation>\n    </message>\n</context>\n<context>\n    <name>EditSocksHttp</name>\n    <message>\n        <source>Version</source>\n        <translation>Версия</translation>\n    </message>\n    <message>\n        <source>Username</source>\n        <translation>Имя пользователя</translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>Пароль</translation>\n    </message>\n</context>\n<context>\n    <name>EditTrojanVLESS</name>\n    <message>\n        <source>Password</source>\n        <translation>Пароль</translation>\n    </message>\n</context>\n<context>\n    <name>EditVMess</name>\n    <message>\n        <source>Security</source>\n        <translation>Шифрование</translation>\n    </message>\n    <message>\n        <source>Alter Id</source>\n        <translation>Альт. ID</translation>\n    </message>\n    <message>\n        <source>UUID</source>\n        <translation>UUID</translation>\n    </message>\n    <message>\n        <source>Generate UUID</source>\n        <translation>Сгенерировать UUID</translation>\n    </message>\n</context>\n<context>\n    <name>GroupItem</name>\n    <message>\n        <source>Update Subscription</source>\n        <translation>Обновить</translation>\n    </message>\n    <message>\n        <source>Edit</source>\n        <translation>Изменить</translation>\n    </message>\n    <message>\n        <source>Remove</source>\n        <translation>Удалить</translation>\n    </message>\n    <message>\n        <source>Basic</source>\n        <translation>Простая</translation>\n    </message>\n    <message>\n        <source>Subscription</source>\n        <translation>Подписка</translation>\n    </message>\n    <message>\n        <source>Archive</source>\n        <translation>Архив</translation>\n    </message>\n    <message>\n        <source>Last update: %1</source>\n        <translation>Последнее обновление: %1</translation>\n    </message>\n    <message>\n        <source>Confirmation</source>\n        <translation>Подтверждение</translation>\n    </message>\n    <message>\n        <source>Remove %1?</source>\n        <translation>Удалить %1?</translation>\n    </message>\n</context>\n<context>\n    <name>JsonEditor</name>\n    <message>\n        <source>JSON Editor</source>\n        <translation>Редактор JSON</translation>\n    </message>\n    <message>\n        <source>Format JSON</source>\n        <translation>Форматировать JSON</translation>\n    </message>\n    <message>\n        <source>Remove All Comments</source>\n        <translation>Удалить все комментарии</translation>\n    </message>\n    <message>\n        <source>Json Editor</source>\n        <translation>Редактор JSON</translation>\n    </message>\n    <message>\n        <source>Structure Preview</source>\n        <translation>Предпросмотр структуры</translation>\n    </message>\n    <message>\n        <source>OK</source>\n        <translation>OK</translation>\n    </message>\n    <message>\n        <source>Json Contains Syntax Errors</source>\n        <translation>JSON содержит синтаксические ошибки</translation>\n    </message>\n    <message>\n        <source>Original Json may contain syntax errors. Json tree is disabled.</source>\n        <translation>Кажется, оригинальный JSON содержит синтатсические ошибки. Дерево JSON отключено.</translation>\n    </message>\n    <message>\n        <source>You must correct these errors before continuing.</source>\n        <translation>Вы должны исправить эти ошибки чтобы продолжить.</translation>\n    </message>\n    <message>\n        <source>Syntax Errors</source>\n        <translation>Синтаксические ошибки</translation>\n    </message>\n    <message>\n        <source>Please fix the JSON errors or remove the comments before continue</source>\n        <translation>Пожалуйста, чтобы продолжить, исправьте ошибки в JSON или удалите комментарии</translation>\n    </message>\n</context>\n<context>\n    <name>MainWindow</name>\n    <message>\n        <source>Program</source>\n        <translation>Программа</translation>\n    </message>\n    <message>\n        <source>Preferences</source>\n        <translation>Настройки</translation>\n    </message>\n    <message>\n        <source>Server</source>\n        <translation>Сервер</translation>\n    </message>\n    <message>\n        <source>Ads</source>\n        <translation>Реклама</translation>\n    </message>\n    <message>\n        <source>Document</source>\n        <translation>Документация</translation>\n    </message>\n    <message>\n        <source>Update</source>\n        <translation>Обновление</translation>\n    </message>\n    <message>\n        <source>Tun Mode</source>\n        <translation>Режим TUN</translation>\n    </message>\n    <message>\n        <source>System Proxy</source>\n        <translation>Режим системного прокси</translation>\n    </message>\n    <message>\n        <source>Type</source>\n        <translation>Тип</translation>\n    </message>\n    <message>\n        <source>Address</source>\n        <translation>Адрес</translation>\n    </message>\n    <message>\n        <source>Name</source>\n        <translation>Имя</translation>\n    </message>\n    <message>\n        <source>Test Result</source>\n        <translation>Результат теста</translation>\n    </message>\n    <message>\n        <source>Traffic</source>\n        <translation>Трафик</translation>\n    </message>\n    <message>\n        <source>Log</source>\n        <translation>Журнал</translation>\n    </message>\n    <message>\n        <source>Connection</source>\n        <translation>Подключение</translation>\n    </message>\n    <message>\n        <source>Status</source>\n        <translation>Статус</translation>\n    </message>\n    <message>\n        <source>Outbound</source>\n        <translation>Outbound</translation>\n    </message>\n    <message>\n        <source>Destination</source>\n        <translation>Пункт назначения</translation>\n    </message>\n    <message>\n        <source>Active Server</source>\n        <translation>Активный сервер</translation>\n    </message>\n    <message>\n        <source>Active Routing</source>\n        <translation>Активное правило роутинга</translation>\n    </message>\n    <message>\n        <source>Share</source>\n        <translation>Поделиться</translation>\n    </message>\n    <message>\n        <source>Current Group</source>\n        <translation>Текущая группа</translation>\n    </message>\n    <message>\n        <source>Current Select</source>\n        <translation>Текущий выбор</translation>\n    </message>\n    <message>\n        <source>Exit</source>\n        <translation>Выход</translation>\n    </message>\n    <message>\n        <source>Show Window</source>\n        <translation>Показать окно</translation>\n    </message>\n    <message>\n        <source>Basic Settings</source>\n        <translation>Основные настройки</translation>\n    </message>\n    <message>\n        <source>New profile</source>\n        <translation>Новый профиль</translation>\n    </message>\n    <message>\n        <source>Groups</source>\n        <translation>Группы</translation>\n    </message>\n    <message>\n        <source>Start</source>\n        <translation>Запустить</translation>\n    </message>\n    <message>\n        <source>Stop</source>\n        <translation>Остановить</translation>\n    </message>\n    <message>\n        <source>Routing Settings</source>\n        <translation>Настройки маршрутов</translation>\n    </message>\n    <message>\n        <source>Add profile from clipboard</source>\n        <translation>Добавить профиль из буфера обмена</translation>\n    </message>\n    <message>\n        <source>Delete</source>\n        <translation>Удалить</translation>\n    </message>\n    <message>\n        <source>Debug Info</source>\n        <translation>Отладочная информация</translation>\n    </message>\n    <message>\n        <source>QR Code and link</source>\n        <translation>QR-код и ссылка</translation>\n    </message>\n    <message>\n        <source>Copy Link</source>\n        <translation>Скопировать ссылку</translation>\n    </message>\n    <message>\n        <source>Clear Test Result</source>\n        <translation>Очистить результат теста</translation>\n    </message>\n    <message>\n        <source>Export %1 config</source>\n        <translation>Экспортировать конфиг %1</translation>\n    </message>\n    <message>\n        <source>Reset Traffic</source>\n        <translation>Сбросить трафик</translation>\n    </message>\n    <message>\n        <source>Scan QR Code</source>\n        <translation>Сканировать QR-код</translation>\n    </message>\n    <message>\n        <source>Enable System Proxy</source>\n        <translation>Активировать системный прокси</translation>\n    </message>\n    <message>\n        <source>Disable</source>\n        <translation>Отключить</translation>\n    </message>\n    <message>\n        <source>Remove Duplicates</source>\n        <translation>Удалить дубликаты</translation>\n    </message>\n    <message>\n        <source>fake</source>\n        <translation>фейк</translation>\n    </message>\n    <message>\n        <source>Move</source>\n        <translation>Переместить</translation>\n    </message>\n    <message>\n        <source>Start with system</source>\n        <translation>Запускаться вместе с системой</translation>\n    </message>\n    <message>\n        <source>Remember last profile</source>\n        <translation>Запомнить последний профиль</translation>\n    </message>\n    <message>\n        <source>Allow other devices to connect</source>\n        <translation>Разрешить подключаться другим устройствам</translation>\n    </message>\n    <message>\n        <source>Remove Unavailable</source>\n        <translation>Удалить недоступные</translation>\n    </message>\n    <message>\n        <source>Full Test</source>\n        <translation>Полный тест</translation>\n    </message>\n    <message>\n        <source>Hotkey Settings</source>\n        <translation>Настройки комбинаций клавиш</translation>\n    </message>\n    <message>\n        <source>Select All</source>\n        <translation>Выбрать всё</translation>\n    </message>\n    <message>\n        <source>Copy links of selected (Neko Links)</source>\n        <translation>Скопировать ссылки для выбранных (Neko links)</translation>\n    </message>\n    <message>\n        <source>Copy links of selected</source>\n        <translation>Скопировать ссылки для выбранных</translation>\n    </message>\n    <message>\n        <source>Enable Tun</source>\n        <translation>Включить TUN-режим</translation>\n    </message>\n    <message>\n        <source>Clone</source>\n        <translation>Клонировать</translation>\n    </message>\n    <message>\n        <source>Update subscription</source>\n        <translation>Обновить подписки</translation>\n    </message>\n    <message>\n        <source>Resolve domain</source>\n        <translatorcomment>Ну слово &quot;разрешить&quot; можно использоваться и в значении &quot;разобраться&quot;</translatorcomment>\n        <translation>Разрешить доменное имя</translation>\n    </message>\n    <message>\n        <source>Tun Settings</source>\n        <translation>Настройки TUN-режима</translation>\n    </message>\n    <message>\n        <source>Restart Program</source>\n        <translation>Перезапустить программу</translation>\n    </message>\n    <message>\n        <source>Open Config Folder</source>\n        <translation>Открыть папку с конфигами</translation>\n    </message>\n    <message>\n        <source>Restart Proxy</source>\n        <translation>Перезапустить прокси</translation>\n    </message>\n    <message>\n        <source>Load routing and apply: %1</source>\n        <translation>Загрузить маршруты и активировать: %1</translation>\n    </message>\n    <message>\n        <source>Error</source>\n        <translation>Ошибка</translation>\n    </message>\n    <message>\n        <source>Tun Settings changed</source>\n        <translation>Настройки TUN изменились</translation>\n    </message>\n    <message>\n        <source>Restart Tun to take effect.</source>\n        <translation>Перезапустите TUN чтобы применить изменения.</translation>\n    </message>\n    <message>\n        <source>Confirmation</source>\n        <translation>Подтверждение</translation>\n    </message>\n    <message>\n        <source>Settings changed, restart proxy?</source>\n        <translation>Настройки изменены, перезапустить прокси?</translation>\n    </message>\n    <message>\n        <source>Settings changed</source>\n        <translation>Настройки изменены</translation>\n    </message>\n    <message>\n        <source>Restart the program to take effect.</source>\n        <translation>Перезапустите программу чтобы новые настройки вступили в силу.</translation>\n    </message>\n    <message>\n        <source>Imported %1 profile(s)</source>\n        <translation>Импортирован(ы) %1 профиль(ей)</translation>\n    </message>\n    <message>\n        <source>Please run NekoBox as admin</source>\n        <translation>Пожалуйста, запустите NekoBox с правами администратора</translation>\n    </message>\n    <message>\n        <source>Current server is incompatible with Tun. Please stop the server first, enable Tun Mode, and then restart.</source>\n        <translation>Текущий сервер не совместим с TUN-режимом. Пожалуйста, сначала остановите подключение к серверу, активируйте TUN-режим, и потом перезапустите.</translation>\n    </message>\n    <message>\n        <source>Not Running</source>\n        <translation>Не запущен</translation>\n    </message>\n    <message>\n        <source>Select</source>\n        <translation>Выбор</translation>\n    </message>\n    <message>\n        <source>Select mode, double-click or press Enter to select a profile, press ESC to exit.</source>\n        <translation>Режим выбора, дважды кликните или нажмите Enter для выбора профиля, либо ESC чтобы выйти.</translation>\n    </message>\n    <message>\n        <source>Clone %1 item(s)</source>\n        <translation>Клонировать %1 записей</translation>\n    </message>\n    <message>\n        <source>Move %1 item(s)</source>\n        <translation>Переместить %1 записей</translation>\n    </message>\n    <message>\n        <source>Remove %1 item(s) ?</source>\n        <translation>Удалить %1 записей ?</translation>\n    </message>\n    <message>\n        <source>Copied %1 item(s)</source>\n        <translation>Скопировано %1 записей</translation>\n    </message>\n    <message>\n        <source>Config copied</source>\n        <translation>Конфиг скопирован</translation>\n    </message>\n    <message>\n        <source>QR Code not found</source>\n        <translation>QR-код не найден</translation>\n    </message>\n    <message>\n        <source>Resolving domain to IP, if support.</source>\n        <translation>Отрезолвить домен в IP-адрес, если поддерживается.</translation>\n    </message>\n    <message>\n        <source>Set ignore keyword</source>\n        <translation>Ключевые слова для игнорирования</translation>\n    </message>\n    <message>\n        <source>Set the following keywords to ignore?\nSplit by line.</source>\n        <translation>Задайте ключнвые слова для игнорирования,\nкаждое на отдельной строке.</translation>\n    </message>\n    <message>\n        <source>Save as route</source>\n        <translation>Сохранить как маршрут</translation>\n    </message>\n    <message>\n        <source>Edit</source>\n        <translation>Редактировать</translation>\n    </message>\n    <message>\n        <source>Save &quot;%1&quot; as a routing rule?</source>\n        <translation>Сохранить &quot;%1&quot; как профиль маршрутизации?</translation>\n    </message>\n    <message>\n        <source>Clear</source>\n        <translation>Очистить</translation>\n    </message>\n    <message>\n        <source>Start: %1\nEnd: %2</source>\n        <translation>Начало %1 \nКонец %2</translation>\n    </message>\n    <message>\n        <source>Failed to stop Tun process</source>\n        <translation>Не удалось остановить TUN-процесс</translation>\n    </message>\n    <message>\n        <source>Test Options</source>\n        <translation>Параметры тестирования</translation>\n    </message>\n    <message>\n        <source>Latency</source>\n        <translation>Задержка</translation>\n    </message>\n    <message>\n        <source>UDP latency</source>\n        <translation>Задержка UDP</translation>\n    </message>\n    <message>\n        <source>Download speed</source>\n        <translation>Скорость загрузки</translation>\n    </message>\n    <message>\n        <source>In and Out IP</source>\n        <translation>Входящий и исходящий IP</translation>\n    </message>\n    <message>\n        <source>[%1] test error: %2</source>\n        <translation>[%1] ошибка теста: %2</translation>\n    </message>\n    <message>\n        <source>Testing</source>\n        <translation>Тестируем</translation>\n    </message>\n    <message>\n        <source>Unavailable</source>\n        <translation>Недоступен</translation>\n    </message>\n    <message>\n        <source>If there is no response for a long time, it is recommended to restart the software.</source>\n        <translation>Если нет ответа в течении долгого времени, рекомендуем перезапустить приложение.</translation>\n    </message>\n    <message>\n        <source>Starting profile %1</source>\n        <translation>Запускаем профиль %1</translation>\n    </message>\n    <message>\n        <source>Failed to start profile %1</source>\n        <translation>Не удалось запустить профиль %1</translation>\n    </message>\n    <message>\n        <source>Stopping profile %1</source>\n        <translation>Останавливаем профиль %1</translation>\n    </message>\n    <message>\n        <source>Failed to stop, please restart the program.</source>\n        <translation>Не удалось остановить, пожалуйста, перезапустите приложение.</translation>\n    </message>\n    <message>\n        <source>Stop Testing</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n    <message>\n        <source>URL Test</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>ProxyItem</name>\n    <message>\n        <source>Confirmation</source>\n        <translation>Подтверждение</translation>\n    </message>\n    <message>\n        <source>Remove %1?</source>\n        <translation>Удалить %1?</translation>\n    </message>\n</context>\n<context>\n    <name>QGuiApplication</name>\n    <message>\n        <source>QT_LAYOUT_DIRECTION</source>\n        <translation></translation>\n    </message>\n</context>\n<context>\n    <name>QObject</name>\n    <message>\n        <source>Core not found: %1</source>\n        <translation>Ядро не найдено: %1</translation>\n    </message>\n    <message>\n        <source>Unavailable</source>\n        <translation>Недоступно</translation>\n    </message>\n    <message>\n        <source>Proxy: %1\nDirect: %2</source>\n        <translation>Через прокси: %1\nНапрямую: %2</translation>\n    </message>\n    <message>\n        <source>Chain Proxy</source>\n        <translation>Цепочка прокси</translation>\n    </message>\n    <message>\n        <source>Request with proxy but no profile started.</source>\n        <translation>Запрос через прокси, но профиль не запущен.</translation>\n    </message>\n    <message>\n        <source>As Subscription (add to this group)</source>\n        <translation>Как подписку (добавить в эту группу)</translation>\n    </message>\n    <message>\n        <source>As Subscription (create new group)</source>\n        <translation>Как подписку (создать новую группу)</translation>\n    </message>\n    <message>\n        <source>As link</source>\n        <translation>Как ссылку</translation>\n    </message>\n    <message>\n        <source>url detected</source>\n        <translation>Обнаружен URL</translation>\n    </message>\n    <message>\n        <source>%1\nHow to update?</source>\n        <translation>%1\nКак обновить?</translation>\n    </message>\n    <message>\n        <source>Requesting subscription: %1</source>\n        <translation>Запрашиваем подписку: %1</translation>\n    </message>\n    <message>\n        <source>Requesting subscription %1 error: %2</source>\n        <translation>Запрашиваем подписку %1 ошибка: %2</translation>\n    </message>\n    <message>\n        <source>Subscription request fininshed: %1</source>\n        <translation>Запрос подписки завершен: %1</translation>\n    </message>\n    <message>\n        <source>Clearing servers...</source>\n        <translation>Очишаем серверы...</translation>\n    </message>\n    <message>\n        <source>Added %1 profiles:\n%2\nDeleted %3 Profiles:\n%4</source>\n        <translation>Добавлено %1 профилей:\n%2\nУдалено %3 профилей:\n%4</translation>\n    </message>\n    <message>\n        <source>Nothing</source>\n        <translation>Ничего</translation>\n    </message>\n    <message>\n        <source>Change of %1:</source>\n        <translation>Изменение %1:</translation>\n    </message>\n    <message>\n        <source>Core exits too frequently, stop automatic restart this profile.</source>\n        <translation>Ядро слишком часто прекращает свою работу, отмена автоматического перезапуска этого профиля.</translation>\n    </message>\n    <message>\n        <source>Core exited, restarting.</source>\n        <translation>Ядро прекратило свою работу, перезапуск.</translation>\n    </message>\n    <message>\n        <source>Select</source>\n        <translation>Выбор</translation>\n    </message>\n    <message>\n        <source>Update</source>\n        <translation>Обновление</translation>\n    </message>\n    <message>\n        <source>No update</source>\n        <translation>Нет обновлений</translation>\n    </message>\n    <message>\n        <source>Update found: %1\nRelease note:\n%2</source>\n        <translation>Найдено обновление: %1\nПримечания к выпуску:\n%2</translation>\n    </message>\n    <message>\n        <source>Open in browser</source>\n        <translation>Открыть в браузере</translation>\n    </message>\n    <message>\n        <source>Close</source>\n        <translation>Закрыть</translation>\n    </message>\n    <message>\n        <source>Update is ready, restart to install?</source>\n        <translation>Обновление готово, перезапуститься для установки?</translation>\n    </message>\n    <message>\n        <source>Used: %1 Remain: %2 Expire: %3</source>\n        <translation>Использовано: %1, осталось: %2, истекло: %3</translation>\n    </message>\n    <message>\n        <source>Default</source>\n        <translation>По умолчанию</translation>\n    </message>\n    <message>\n        <source>The last speed test did not exit completely, please wait. If it persists, please restart the program.</source>\n        <translation type=\"unfinished\"></translation>\n    </message>\n</context>\n<context>\n    <name>Qv2ray::ui::widgets::AutoCompleteTextEdit</name>\n    <message>\n        <source>You can not input space characters here.</source>\n        <translation>Сюда нельзя вводить пробелы.</translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "translations/translations.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/translations\">\n        <file>zh_CN.qm</file>\n        <file>fa_IR.qm</file>\n        <file>ru_RU.qm</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "translations/zh_CN.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"zh_CN\">\n<context>\n    <name>DialogBasicSettings</name>\n    <message>\n        <source>Basic Settings</source>\n        <translation>基本设置</translation>\n    </message>\n    <message>\n        <source>Enable</source>\n        <translation>启用</translation>\n    </message>\n    <message>\n        <source>Listen Address</source>\n        <translation>监听地址</translation>\n    </message>\n    <message>\n        <source>concurrency</source>\n        <translation>并发</translation>\n    </message>\n    <message>\n        <source>User Agent</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Common</source>\n        <translation>通用</translation>\n    </message>\n    <message>\n        <source>Style</source>\n        <translation>样式</translation>\n    </message>\n    <message>\n        <source>Theme</source>\n        <translation>主题</translation>\n    </message>\n    <message>\n        <source>System</source>\n        <translation>系统</translation>\n    </message>\n    <message>\n        <source>Subscription</source>\n        <translation>订阅</translation>\n    </message>\n    <message>\n        <source>Core</source>\n        <translation>核心</translation>\n    </message>\n    <message>\n        <source>Extra Core</source>\n        <translation>其他核心</translation>\n    </message>\n    <message>\n        <source>Select</source>\n        <translation>选择</translation>\n    </message>\n    <message>\n        <source>Edit</source>\n        <translation>编辑</translation>\n    </message>\n    <message>\n        <source>Custom Inbound</source>\n        <translation>自定义入站</translation>\n    </message>\n    <message>\n        <source>Concurrent</source>\n        <translation>并发</translation>\n    </message>\n    <message>\n        <source>Use proxy when updating subscription</source>\n        <translation>更新订阅时使用代理</translation>\n    </message>\n    <message>\n        <source>Security</source>\n        <translation>安全</translation>\n    </message>\n    <message>\n        <source>Statistics refresh rate</source>\n        <translation>流量统计刷新率</translation>\n    </message>\n    <message>\n        <source>Off</source>\n        <translation>关闭</translation>\n    </message>\n    <message>\n        <source>Add</source>\n        <translation>添加</translation>\n    </message>\n    <message>\n        <source>Delete</source>\n        <translation>删除</translation>\n    </message>\n    <message>\n        <source>Please input the core name.</source>\n        <translation>请输入核心名.</translation>\n    </message>\n    <message>\n        <source>Please select the core name.</source>\n        <translation>请选择核心名.</translation>\n    </message>\n    <message>\n        <source>Connection statistics</source>\n        <translation>连接统计</translation>\n    </message>\n    <message>\n        <source>Include Pre-release when checking update</source>\n        <translation>检查更新时包括 Pre-release 版本</translation>\n    </message>\n    <message>\n        <source>Set custom icon</source>\n        <translation>自定义图标</translation>\n    </message>\n    <message>\n        <source>Please select a PNG file.</source>\n        <translation>请选择一个 PNG 文件。</translation>\n    </message>\n    <message>\n        <source>Reset</source>\n        <translation>重置</translation>\n    </message>\n    <message>\n        <source>Please select a valid square image.</source>\n        <translation>请选择有效的正方形图像。</translation>\n    </message>\n    <message>\n        <source>Cancel</source>\n        <translation>取消</translation>\n    </message>\n    <message>\n        <source>System proxy format</source>\n        <translation>系统代理格式</translation>\n    </message>\n    <message>\n        <source>Advanced system proxy settings. Please select a format.</source>\n        <translation>高级系统代理设置。请选择一种格式。</translation>\n    </message>\n    <message>\n        <source>Old Share Link Format</source>\n        <translation>旧分享链接格式</translation>\n    </message>\n    <message>\n        <source>Share VMess Link with v2rayN Format</source>\n        <translation>用 v2rayN 的格式分享 VMess 链接</translation>\n    </message>\n    <message>\n        <source>Clear servers before updating subscription</source>\n        <translation>更新订阅前清除服务器</translation>\n    </message>\n    <message>\n        <source>Ignore TLS errors when updating subscription</source>\n        <translation>更新订阅时忽略 TLS 错误</translation>\n    </message>\n    <message>\n        <source>Hide dashboard at startup</source>\n        <translation>启动时不显示仪表盘</translation>\n    </message>\n    <message>\n        <source>Max log lines</source>\n        <translation>日志最大行数限制</translation>\n    </message>\n    <message>\n        <source>Inbound Auth</source>\n        <translation>入站认证设置</translation>\n    </message>\n    <message>\n        <source>Username</source>\n        <translation>用户名</translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>密码</translation>\n    </message>\n    <message>\n        <source>Skip TLS certificate authentication by default (allowInsecure)</source>\n        <translation>默认跳过 TLS 证书验证 (allowInsecure)</translation>\n    </message>\n    <message>\n        <source>Default uTLS Fingerprint</source>\n        <translation>默认 uTLS 指纹</translation>\n    </message>\n    <message>\n        <source>Core Options</source>\n        <translation>核心选项</translation>\n    </message>\n    <message>\n        <source>Override underlying DNS</source>\n        <translation>覆盖底层 DNS</translation>\n    </message>\n    <message>\n        <source>Default On</source>\n        <translation>默认开启</translation>\n    </message>\n    <message>\n        <source>Multiplex (mux)</source>\n        <translation>多路复用 Mux</translation>\n    </message>\n    <message>\n        <source>Latency Test URL</source>\n        <translation>延迟测试 URL</translation>\n    </message>\n    <message>\n        <source>Download Test URL</source>\n        <translation>下载测试 URL</translation>\n    </message>\n    <message>\n        <source>Timeout (s)</source>\n        <translation>超时（秒）</translation>\n    </message>\n    <message>\n        <source>Automatic update</source>\n        <translation>自动更新订阅</translation>\n    </message>\n    <message>\n        <source>Interval (minute, invalid if less than 30)</source>\n        <translation>时间间隔（分钟，少于 30 分钟无效）</translation>\n    </message>\n    <message>\n        <source>Mixed (SOCKS+HTTP) Listen Port</source>\n        <translation>Mixed (SOCKS+HTTP) 监听端口</translation>\n    </message>\n</context>\n<context>\n    <name>DialogEditGroup</name>\n    <message>\n        <source>Edit Group</source>\n        <translation>编辑分组</translation>\n    </message>\n    <message>\n        <source>Type</source>\n        <translation>类型</translation>\n    </message>\n    <message>\n        <source>Name</source>\n        <translation>名称</translation>\n    </message>\n    <message>\n        <source>Basic</source>\n        <translation>基本</translation>\n    </message>\n    <message>\n        <source>Subscription</source>\n        <translation>订阅</translation>\n    </message>\n    <message>\n        <source>URL</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Archive</source>\n        <translation>归档</translation>\n    </message>\n    <message>\n        <source>Warning</source>\n        <translation>警告</translation>\n    </message>\n    <message>\n        <source>Please input URL</source>\n        <translation>请输入 URL</translation>\n    </message>\n    <message>\n        <source>Copy profile share links</source>\n        <translation>复制分组内配置的分享链接</translation>\n    </message>\n    <message>\n        <source>Copied</source>\n        <translation>复制成功</translation>\n    </message>\n    <message>\n        <source>Copy profile share links (Neko Links)</source>\n        <translation>复制分组内配置的分享链接 (Neko Links)</translation>\n    </message>\n    <message>\n        <source>Manually column width</source>\n        <translation>手动调节列宽</translation>\n    </message>\n    <message>\n        <source>Front Proxy</source>\n        <translation>前置代理</translation>\n    </message>\n    <message>\n        <source>None</source>\n        <translation>无</translation>\n    </message>\n    <message>\n        <source>Clear</source>\n        <translation>清除</translation>\n    </message>\n    <message>\n        <source>Skip automatic update</source>\n        <translation>跳过自动更新</translation>\n    </message>\n    <message>\n        <source>Common</source>\n        <translation>通用</translation>\n    </message>\n    <message>\n        <source>Share</source>\n        <translation>分享</translation>\n    </message>\n</context>\n<context>\n    <name>DialogEditProfile</name>\n    <message>\n        <source>Edit</source>\n        <translation>编辑</translation>\n    </message>\n    <message>\n        <source>Common</source>\n        <translation>通用</translation>\n    </message>\n    <message>\n        <source>Type</source>\n        <translation>类型</translation>\n    </message>\n    <message>\n        <source>Port</source>\n        <translation>端口</translation>\n    </message>\n    <message>\n        <source>Address</source>\n        <translation>地址</translation>\n    </message>\n    <message>\n        <source>Name</source>\n        <translation>名称</translation>\n    </message>\n    <message>\n        <source>Network</source>\n        <translation>传输</translation>\n    </message>\n    <message>\n        <source>Security</source>\n        <translation>传输层安全</translation>\n    </message>\n    <message>\n        <source>Network Settings (%1)</source>\n        <translation>传输设置 (%1)</translation>\n    </message>\n    <message>\n        <source>The underlying transport method. It must be consistent with the server, otherwise, the connection cannot be established.</source>\n        <translation>底层传输方式。必须与服务器一致，否则无法建立连接。</translation>\n    </message>\n    <message>\n        <source>Transport Layer Security. It must be consistent with the server, otherwise, the connection cannot be established.</source>\n        <translation>传输层安全。必须与服务器一致，否则无法建立连接。</translation>\n    </message>\n    <message>\n        <source>UDP FullCone Packet encoding for implementing features such as UDP FullCone. Server support is required, if the wrong selection is made, the connection cannot be made. Please leave it blank.</source>\n        <translation>包编码，用于实现 UDP FullCone 等特性。需要服务器支持，选错无法连接。不懂请留空。</translation>\n    </message>\n    <message>\n        <source>When enabled, V2Ray will not check the validity of the TLS certificate provided by the remote host (the security is equivalent to plaintext)</source>\n        <translation>开启后 V2Ray 不会检查远端主机所提供的 TLS 证书的有效性（安全性相当于明文）</translation>\n    </message>\n    <message>\n        <source>Server name indication, clear text.</source>\n        <translation>服务器名称指示，明文。</translation>\n    </message>\n    <message>\n        <source>Application layer protocol negotiation, clear text. Please separate them with commas.</source>\n        <translation>应用层协议协商，明文。多个请以英文逗号分隔。</translation>\n    </message>\n    <message>\n        <source>Allow insecure</source>\n        <translation>不检查服务器证书</translation>\n    </message>\n    <message>\n        <source>Certificate</source>\n        <translation>证书</translation>\n    </message>\n    <message>\n        <source>Not set</source>\n        <translation>未设置</translation>\n    </message>\n    <message>\n        <source>Already set</source>\n        <translation>已设置</translation>\n    </message>\n    <message>\n        <source>Packet Encoding</source>\n        <translation>包编码</translation>\n    </message>\n    <message>\n        <source>Settings</source>\n        <translation>设置</translation>\n    </message>\n    <message>\n        <source>Custom (Extra Core)</source>\n        <translation>自定义 (其他核心)</translation>\n    </message>\n    <message>\n        <source>TLS Security Settings</source>\n        <translation>TLS 安全设置</translation>\n    </message>\n    <message>\n        <source>TLS Camouflage Settings</source>\n        <translation>TLS 伪装设置</translation>\n    </message>\n    <message>\n        <source>Reality public key. If not empty, turn TLS into REALITY.</source>\n        <translation>Reality public key. 如果不为空，则将 TLS 变为 REALITY。</translation>\n    </message>\n    <message>\n        <source>Custom (%1 outbound)</source>\n        <translation>自定义 (%1 出站)</translation>\n    </message>\n    <message>\n        <source>Custom (%1 config)</source>\n        <translation>自定义 (%1 完整配置)</translation>\n    </message>\n    <message>\n        <source>Custom Outbound Settings</source>\n        <translation>自定义出站 JSON 设置</translation>\n    </message>\n    <message>\n        <source>Custom Config Settings</source>\n        <translation>自定义配置 JSON 设置</translation>\n    </message>\n    <message>\n        <source>Apply settings to this group</source>\n        <translation>将设置应用于该组</translation>\n    </message>\n    <message>\n        <source>Multiplex</source>\n        <translation>多路复用</translation>\n    </message>\n    <message>\n        <source>Keep Default</source>\n        <translation>保持默认</translation>\n    </message>\n    <message>\n        <source>On</source>\n        <translation>开启</translation>\n    </message>\n    <message>\n        <source>Off</source>\n        <translation>关闭</translation>\n    </message>\n    <message>\n        <source>Confirm</source>\n        <translation>确认</translation>\n    </message>\n    <message>\n        <source>Server support is required</source>\n        <translation>需要服务器支持</translation>\n    </message>\n    <message>\n        <source>Reality short id. Accept only one value.</source>\n        <translation>Reality short id. 只接受一个值。</translation>\n    </message>\n</context>\n<context>\n    <name>DialogHotkey</name>\n    <message>\n        <source>Hotkey</source>\n        <translation>热键</translation>\n    </message>\n    <message>\n        <source>Show groups</source>\n        <translation>显示分组</translation>\n    </message>\n    <message>\n        <source>Show routes</source>\n        <translation>显示路由</translation>\n    </message>\n    <message>\n        <source>Trigger main window</source>\n        <translation>显示/隐藏主窗口</translation>\n    </message>\n    <message>\n        <source>System Proxy</source>\n        <translation>系统代理</translation>\n    </message>\n</context>\n<context>\n    <name>DialogManageGroups</name>\n    <message>\n        <source>Groups</source>\n        <translation>分组</translation>\n    </message>\n    <message>\n        <source>New group</source>\n        <translation>新建分组</translation>\n    </message>\n    <message>\n        <source>Update all subscriptions</source>\n        <translation>更新所有订阅</translation>\n    </message>\n    <message>\n        <source>Confirmation</source>\n        <translation>确认</translation>\n    </message>\n    <message>\n        <source>Update all subscriptions?</source>\n        <translation>更新所有订阅？</translation>\n    </message>\n</context>\n<context>\n    <name>DialogManageRoutes</name>\n    <message>\n        <source>Routes</source>\n        <translation>路由</translation>\n    </message>\n    <message>\n        <source>Disable</source>\n        <translation>禁用</translation>\n    </message>\n    <message>\n        <source>Sniffing Mode</source>\n        <translation>流量探测</translation>\n    </message>\n    <message>\n        <source>Sniff result for routing</source>\n        <translation>探测结果用于路由判断</translation>\n    </message>\n    <message>\n        <source>Sniff result for destination</source>\n        <translation>探测结果用于目标地址</translation>\n    </message>\n    <message>\n        <source>Direct DNS</source>\n        <translation>直连 DNS</translation>\n    </message>\n    <message>\n        <source>Remote DNS</source>\n        <translation>远程 DNS</translation>\n    </message>\n    <message>\n        <source>Enable DNS Routing</source>\n        <translation>启用 DNS 路由</translation>\n    </message>\n    <message>\n        <source>Block</source>\n        <translation>阻止</translation>\n    </message>\n    <message>\n        <source>Direct</source>\n        <translation>直连</translation>\n    </message>\n    <message>\n        <source>Domain</source>\n        <translation>域名</translation>\n    </message>\n    <message>\n        <source>Proxy</source>\n        <translation>代理</translation>\n    </message>\n    <message>\n        <source>Preset</source>\n        <translation>预设</translation>\n    </message>\n    <message>\n        <source>Bypass LAN and China</source>\n        <translation>绕过局域网和大陆</translation>\n    </message>\n    <message>\n        <source>Global</source>\n        <translation>全局</translation>\n    </message>\n    <message>\n        <source>IP</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Save</source>\n        <translation>保存</translation>\n    </message>\n    <message>\n        <source>Load</source>\n        <translation>加载</translation>\n    </message>\n    <message>\n        <source>Cancel</source>\n        <translation>取消</translation>\n    </message>\n    <message>\n        <source>Remove</source>\n        <translation>删除</translation>\n    </message>\n    <message>\n        <source>Save routing: %1</source>\n        <translation>保存路由: %1</translation>\n    </message>\n    <message>\n        <source>Load routing: %1</source>\n        <translation>加载路由: %1</translation>\n    </message>\n    <message>\n        <source>Remove routing: %1</source>\n        <translation>删除路由: %1</translation>\n    </message>\n    <message>\n        <source>Mange route set</source>\n        <translation>管理路由规则</translation>\n    </message>\n    <message>\n        <source>Default Outbound</source>\n        <translation>默认出站</translation>\n    </message>\n    <message>\n        <source>Domain Strategy</source>\n        <translation>域名策略</translation>\n    </message>\n    <message>\n        <source>Server Address Strategy</source>\n        <translation>服务器地址策略</translation>\n    </message>\n    <message>\n        <source>Common</source>\n        <translation>通用</translation>\n    </message>\n    <message>\n        <source>DNS</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Simple DNS Settings</source>\n        <translation>简易 DNS 设置</translation>\n    </message>\n    <message>\n        <source>Use DNS Object</source>\n        <translation>使用 DNS Object</translation>\n    </message>\n    <message>\n        <source>DNS Object Settings</source>\n        <translation>DNS Object 设置</translation>\n    </message>\n    <message>\n        <source>Simple Route</source>\n        <translation>简易路由</translation>\n    </message>\n    <message>\n        <source>Custom Route</source>\n        <translation>自定义路由</translation>\n    </message>\n    <message>\n        <source>Custom Route (global)</source>\n        <translation>自定义路由（全局）</translation>\n    </message>\n    <message>\n        <source>Note: Other settings are independent for each route set.</source>\n        <translation>注意：其他设置对于每个路由集都是独立的。</translation>\n    </message>\n    <message>\n        <source>Route sets</source>\n        <translation>路由集</translation>\n    </message>\n    <message>\n        <source>Query Strategy</source>\n        <translation>查询策略</translation>\n    </message>\n    <message>\n        <source>Document</source>\n        <translation>文档</translation>\n    </message>\n    <message>\n        <source>Format</source>\n        <translation>格式化</translation>\n    </message>\n    <message>\n        <source>This is especially important and it is recommended to use the default value of &quot;localhost&quot;.\nIf the default value does not work, try changing it to &quot;223.5.5.5&quot;.\nFor more information, see the document &quot;Configuration/DNS&quot;.</source>\n        <translation>此项尤为重要，建议使用默认值 &quot;localhost&quot;。\n如果默认值不工作，可以尝试更改为 &quot;223.5.5.5&quot;。\n更多信息，请参阅文档 &quot;配置/DNS&quot;。</translation>\n    </message>\n    <message>\n        <source>Final DNS Out</source>\n        <translation>默认 DNS 出站</translation>\n    </message>\n</context>\n<context>\n    <name>DialogVPNSettings</name>\n    <message>\n        <source>Tun Settings</source>\n        <translation>Tun 设置</translation>\n    </message>\n    <message>\n        <source>Hide Console</source>\n        <translation>隐藏控制台</translation>\n    </message>\n    <message>\n        <source>Tun Enable IPv6</source>\n        <translation>启用 Tun IPv6</translation>\n    </message>\n    <message>\n        <source>Bypass CIDR</source>\n        <translation>绕过 CIDR</translation>\n    </message>\n    <message>\n        <source>Bypass Process Name</source>\n        <translation>绕过进程名</translation>\n    </message>\n    <message>\n        <source>Whitelist mode</source>\n        <translation>白名单模式</translation>\n    </message>\n    <message>\n        <source>Proxy CIDR</source>\n        <translation>代理 CIDR</translation>\n    </message>\n    <message>\n        <source>Proxy Process Name</source>\n        <translation>代理进程名</translation>\n    </message>\n    <message>\n        <source>Whether blacklisted or whitelisted, your traffic will be handled by nekobox_core (sing-tun). This is NOT equal to &quot;process mode&quot; of some software.</source>\n        <translation>无论是黑名单还是白名单，您的流量都将由 nekobox_core (sing-tun) 处理。这不等于某些软件的「进程模式」。</translation>\n    </message>\n    <message>\n        <source>Troubleshooting</source>\n        <translation>故障排除</translation>\n    </message>\n    <message>\n        <source>If you have trouble starting VPN, you can force reset nekobox_core process here.\n\nIf still not working, see documentation for more information.\nhttps://matsuridayo.github.io/n-configuration/#vpn-tun</source>\n        <translation>如果您在启动 Tun 时遇到问题，您可以在此处强制重置 nekobox_core 进程。\n\n如果仍然无法正常工作，请参阅文档以获取更多信息。\nhttps://matsuridayo.github.io/n-configuration/#vpn-tun</translation>\n    </message>\n    <message>\n        <source>Reset</source>\n        <translation>重置</translation>\n    </message>\n    <message>\n        <source>Cancel</source>\n        <translation>取消</translation>\n    </message>\n    <message>\n        <source>Internal Tun</source>\n        <translation>内部 Tun</translation>\n    </message>\n    <message>\n        <source>Add a tun inbound to the profile startup, instead of using two processes.\nThis needs to be run NekoBox with administrator privileges.</source>\n        <translation>在配置文件启动时添加一个tun inbound，而不是使用两个进程。\n这需要以管理员权限运行NekoBox。</translation>\n    </message>\n</context>\n<context>\n    <name>EditChain</name>\n    <message>\n        <source>Select Profile</source>\n        <translation>选择配置</translation>\n    </message>\n    <message>\n        <source>Traffic order is from top to bottom</source>\n        <translation>流量顺序是从上到下（最后一个配置为流量的出口）</translation>\n    </message>\n    <message>\n        <source>Name cannot be empty.</source>\n        <translation>名称 不能为空</translation>\n    </message>\n</context>\n<context>\n    <name>EditCustom</name>\n    <message>\n        <source>Core</source>\n        <translation>核心</translation>\n    </message>\n    <message>\n        <source>Command</source>\n        <translation>命令</translation>\n    </message>\n    <message>\n        <source>Json Editor</source>\n        <translation>JSON 编辑器</translation>\n    </message>\n    <message>\n        <source>Please pick a core.</source>\n        <translation>请选择一个核心。</translation>\n    </message>\n    <message>\n        <source>Outbound JSON, please read the documentation.</source>\n        <translation>填写出站 JSON 对象，详细请看文档。</translation>\n    </message>\n    <message>\n        <source>Config Suffix</source>\n        <translation>配置文件后缀</translation>\n    </message>\n    <message>\n        <source>Random if it&apos;s empty or zero.</source>\n        <translation>如果为空或为零，则表示使用随机端口。</translation>\n    </message>\n    <message>\n        <source>Preview</source>\n        <translation>预览</translation>\n    </message>\n    <message>\n        <source>Preview replace</source>\n        <translation>预览替换串</translation>\n    </message>\n    <message>\n        <source>Preview config</source>\n        <translation>预览配置</translation>\n    </message>\n    <message>\n        <source>Please fill the complete config.</source>\n        <translation>请填写完整配置。</translation>\n    </message>\n    <message>\n        <source>Name cannot be empty.</source>\n        <translation>名称 不能为空</translation>\n    </message>\n</context>\n<context>\n    <name>EditNaive</name>\n    <message>\n        <source>Protocol</source>\n        <translation>协议</translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>密码</translation>\n    </message>\n    <message>\n        <source>Extra headers</source>\n        <translation>附加标头</translation>\n    </message>\n    <message>\n        <source>SNI</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Username</source>\n        <translation>用户名</translation>\n    </message>\n    <message>\n        <source>Certificate</source>\n        <translation>证书</translation>\n    </message>\n    <message>\n        <source>Insecure concurrency</source>\n        <translation>不安全并发</translation>\n    </message>\n    <message>\n        <source>Disable logs</source>\n        <translation>关闭日志</translation>\n    </message>\n    <message>\n        <source>Turn on this option if your connection is lost after a while</source>\n        <translation>如果连接一段时间后出现中断，请打开此选项</translation>\n    </message>\n</context>\n<context>\n    <name>EditQUIC</name>\n    <message>\n        <source>Certificate</source>\n        <translation>证书</translation>\n    </message>\n    <message>\n        <source>Download (Mbps)</source>\n        <translation>下载速度 (Mbps)</translation>\n    </message>\n    <message>\n        <source>Disable MTU Discovery</source>\n        <translation>禁用 MTU 探测</translation>\n    </message>\n    <message>\n        <source>Hop Interval (s)</source>\n        <translation>端口跳跃间隔 (秒)</translation>\n    </message>\n    <message>\n        <source>Allow Insecure</source>\n        <translation>不检查服务器证书</translation>\n    </message>\n    <message>\n        <source>Hop Port</source>\n        <translation>跳跃端口</translation>\n    </message>\n    <message>\n        <source>Upload (Mbps)</source>\n        <translation>上传速度 (Mbps)</translation>\n    </message>\n    <message>\n        <source>Obfs Password</source>\n        <translation>混淆密码</translation>\n    </message>\n    <message>\n        <source>SNI</source>\n        <translation>SNI</translation>\n    </message>\n    <message>\n        <source>Generate UUID</source>\n        <translation>生成 UUID</translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>密码</translation>\n    </message>\n    <message>\n        <source>Zero Rtt Handshake</source>\n        <translation>0-Rtt 握手</translation>\n    </message>\n    <message>\n        <source>UDP Relay Mode</source>\n        <translation>UDP 中继模式</translation>\n    </message>\n    <message>\n        <source>Congestion Control</source>\n        <translation>拥塞控制</translation>\n    </message>\n    <message>\n        <source>Heartbeat</source>\n        <translation>心跳包发送间隔</translation>\n    </message>\n    <message>\n        <source>Disable SNI</source>\n        <translation>不发送服务器名称指示</translation>\n    </message>\n    <message>\n        <source>Force use external core</source>\n        <translation>强制使用外部核心</translation>\n    </message>\n</context>\n<context>\n    <name>EditShadowSocks</name>\n    <message>\n        <source>Plugin Args</source>\n        <translation>插件参数</translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>密码</translation>\n    </message>\n    <message>\n        <source>Encryption</source>\n        <translation>加密</translation>\n    </message>\n    <message>\n        <source>Plugin</source>\n        <translation>插件</translation>\n    </message>\n    <message>\n        <source>Version of UDP over TCP protocol, server support is required.</source>\n        <translation>UDP over TCP 协议版本，需要服务器支持。</translation>\n    </message>\n    <message>\n        <source>Off</source>\n        <translation>关闭</translation>\n    </message>\n</context>\n<context>\n    <name>EditSocksHttp</name>\n    <message>\n        <source>Version</source>\n        <translation>版本</translation>\n    </message>\n    <message>\n        <source>Username</source>\n        <translation>用户名</translation>\n    </message>\n    <message>\n        <source>Password</source>\n        <translation>密码</translation>\n    </message>\n</context>\n<context>\n    <name>EditTrojanVLESS</name>\n    <message>\n        <source>Password</source>\n        <translation>密码</translation>\n    </message>\n</context>\n<context>\n    <name>EditVMess</name>\n    <message>\n        <source>Security</source>\n        <translation>加密</translation>\n    </message>\n    <message>\n        <source>Alter Id</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>UUID</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Generate UUID</source>\n        <translation>生成 UUID</translation>\n    </message>\n</context>\n<context>\n    <name>GroupItem</name>\n    <message>\n        <source>Update Subscription</source>\n        <translation>更新订阅</translation>\n    </message>\n    <message>\n        <source>Edit</source>\n        <translation>编辑</translation>\n    </message>\n    <message>\n        <source>Basic</source>\n        <translation>基本</translation>\n    </message>\n    <message>\n        <source>Subscription</source>\n        <translation>订阅</translation>\n    </message>\n    <message>\n        <source>Confirmation</source>\n        <translation>确认</translation>\n    </message>\n    <message>\n        <source>Remove</source>\n        <translation>删除</translation>\n    </message>\n    <message>\n        <source>Remove %1?</source>\n        <translation>删除 %1 ?</translation>\n    </message>\n    <message>\n        <source>Archive</source>\n        <translation>归档</translation>\n    </message>\n    <message>\n        <source>Last update: %1</source>\n        <translation>最后更新 %1</translation>\n    </message>\n</context>\n<context>\n    <name>JsonEditor</name>\n    <message>\n        <source>JSON Editor</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Format JSON</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Remove All Comments</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Json Editor</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Structure Preview</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>OK</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Json Contains Syntax Errors</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Original Json may contain syntax errors. Json tree is disabled.</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>You must correct these errors before continuing.</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Syntax Errors</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Please fix the JSON errors or remove the comments before continue</source>\n        <translation></translation>\n    </message>\n</context>\n<context>\n    <name>MainWindow</name>\n    <message>\n        <source>Program</source>\n        <translation>程序</translation>\n    </message>\n    <message>\n        <source>Preferences</source>\n        <translation>首选项</translation>\n    </message>\n    <message>\n        <source>Server</source>\n        <translation>服务器</translation>\n    </message>\n    <message>\n        <source>Ads</source>\n        <translation>推广</translation>\n    </message>\n    <message>\n        <source>Type</source>\n        <translation>类型</translation>\n    </message>\n    <message>\n        <source>Address</source>\n        <translation>地址</translation>\n    </message>\n    <message>\n        <source>Name</source>\n        <translation>名称</translation>\n    </message>\n    <message>\n        <source>Test Result</source>\n        <translation>测试结果</translation>\n    </message>\n    <message>\n        <source>Traffic</source>\n        <translation>流量</translation>\n    </message>\n    <message>\n        <source>System Proxy</source>\n        <translation>系统代理</translation>\n    </message>\n    <message>\n        <source>Share</source>\n        <translation>分享</translation>\n    </message>\n    <message>\n        <source>Exit</source>\n        <translation>退出</translation>\n    </message>\n    <message>\n        <source>Basic Settings</source>\n        <translation>基本设置</translation>\n    </message>\n    <message>\n        <source>Groups</source>\n        <translation>分组</translation>\n    </message>\n    <message>\n        <source>Stop</source>\n        <translation>停止</translation>\n    </message>\n    <message>\n        <source>Add profile from clipboard</source>\n        <translation>从剪贴板添加</translation>\n    </message>\n    <message>\n        <source>Debug Info</source>\n        <translation>调试信息</translation>\n    </message>\n    <message>\n        <source>Copy Link</source>\n        <translation>复制链接</translation>\n    </message>\n    <message>\n        <source>Clear Test Result</source>\n        <translation>清理测试结果</translation>\n    </message>\n    <message>\n        <source>Scan QR Code</source>\n        <translation>扫描 QR Code</translation>\n    </message>\n    <message>\n        <source>Disable</source>\n        <translation>禁用</translation>\n    </message>\n    <message>\n        <source>Error</source>\n        <translation>错误</translation>\n    </message>\n    <message>\n        <source>Confirmation</source>\n        <translation>确认</translation>\n    </message>\n    <message>\n        <source>Settings changed, restart proxy?</source>\n        <translation>设置已改变，是否重启代理？</translation>\n    </message>\n    <message>\n        <source>Imported %1 profile(s)</source>\n        <translation>导入了 %1 个配置</translation>\n    </message>\n    <message>\n        <source>Unavailable</source>\n        <translation>不可用</translation>\n    </message>\n    <message>\n        <source>Remove %1 item(s) ?</source>\n        <translation>删除 %1 个项目？</translation>\n    </message>\n    <message>\n        <source>Config copied</source>\n        <translation>配置已复制</translation>\n    </message>\n    <message>\n        <source>[%1] test error: %2</source>\n        <translation>[%1] 测试错误: %2</translation>\n    </message>\n    <message>\n        <source>Clear</source>\n        <translation>清除</translation>\n    </message>\n    <message>\n        <source>fake</source>\n        <translation></translation>\n    </message>\n    <message>\n        <source>Testing</source>\n        <translation>正在测试</translation>\n    </message>\n    <message>\n        <source>Update</source>\n        <translation>更新</translation>\n    </message>\n    <message>\n        <source>Document</source>\n        <translation>文档</translation>\n    </message>\n    <message>\n        <source>Select</source>\n        <translation>选择</translation>\n    </message>\n    <message>\n        <source>QR Code not found</source>\n        <translation>未扫描到 QR Code</translation>\n    </message>\n    <message>\n        <source>Move</source>\n        <translation>移动</translation>\n    </message>\n    <message>\n        <source>Log</source>\n        <translation>日志</translation>\n    </message>\n    <message>\n        <source>Connection</source>\n        <translation>连接</translation>\n    </message>\n    <message>\n        <source>Status</source>\n        <translation>状态</translation>\n    </message>\n    <message>\n        <source>Outbound</source>\n        <translation>出站</translation>\n    </message>\n    <message>\n        <source>Destination</source>\n        <translation>目标地址</translation>\n    </message>\n    <message>\n        <source>Start: %1\nEnd: %2</source>\n        <translation>开始: %1\n结束: %2</translation>\n    </message>\n    <message>\n        <source>Starting profile %1</source>\n        <translation>正在启动配置 %1</translation>\n    </message>\n    <message>\n        <source>Stopping profile %1</source>\n        <translation>正在停止配置 %1</translation>\n    </message>\n    <message>\n        <source>Start with system</source>\n        <translation>跟随系统启动</translation>\n    </message>\n    <message>\n        <source>Remember last profile</source>\n        <translation>记住最后的配置</translation>\n    </message>\n    <message>\n        <source>Move %1 item(s)</source>\n        <translation>移动 %1 个项目</translation>\n    </message>\n    <message>\n        <source>Remove Unavailable</source>\n        <translation>删除不可用的配置</translation>\n    </message>\n    <message>\n        <source>New profile</source>\n        <translation>手动输入配置</translation>\n    </message>\n    <message>\n        <source>Hotkey Settings</source>\n        <translation>热键设置</translation>\n    </message>\n    <message>\n        <source>QR Code and link</source>\n        <translation>显示 QR Code 和分享链接</translation>\n    </message>\n    <message>\n        <source>Active Routing</source>\n        <translation>当前路由规则</translation>\n    </message>\n    <message>\n        <source>Active Server</source>\n        <translation>当前服务器</translation>\n    </message>\n    <message>\n        <source>Load routing and apply: %1</source>\n        <translation>加载路由规则并应用: %1</translation>\n    </message>\n    <message>\n        <source>Copied %1 item(s)</source>\n        <translation>复制了 %1 个项目</translation>\n    </message>\n    <message>\n        <source>Full Test</source>\n        <translation>完整测试</translation>\n    </message>\n    <message>\n        <source>Current Group</source>\n        <translation>当前分组</translation>\n    </message>\n    <message>\n        <source>Reset Traffic</source>\n        <translation>重置流量</translation>\n    </message>\n    <message>\n        <source>Remove Duplicates</source>\n        <translation>删除重复的配置</translation>\n    </message>\n    <message>\n        <source>Select All</source>\n        <translation>全选</translation>\n    </message>\n    <message>\n        <source>Tun Mode</source>\n        <translation>Tun 模式</translation>\n    </message>\n    <message>\n        <source>Failed to stop Tun process</source>\n        <translation>停止 Tun 失败</translation>\n    </message>\n    <message>\n        <source>Enable System Proxy</source>\n        <translation>启用系统代理</translation>\n    </message>\n    <message>\n        <source>Enable Tun</source>\n        <translation>启用 Tun</translation>\n    </message>\n    <message>\n        <source>Tun Settings changed</source>\n        <translation>Tun 设置改变</translation>\n    </message>\n    <message>\n        <source>Restart Tun to take effect.</source>\n        <translation>重启 Tun 生效。</translation>\n    </message>\n    <message>\n        <source>Start</source>\n        <translation>启动</translation>\n    </message>\n    <message>\n        <source>Delete</source>\n        <translation>删除</translation>\n    </message>\n    <message>\n        <source>Copy links of selected</source>\n        <translation>批量复制选中项目的分享链接</translation>\n    </message>\n    <message>\n        <source>Clone</source>\n        <translation>克隆</translation>\n    </message>\n    <message>\n        <source>Update subscription</source>\n        <translation>更新订阅</translation>\n    </message>\n    <message>\n        <source>Clone %1 item(s)</source>\n        <translation>克隆 %1 个项目</translation>\n    </message>\n    <message>\n        <source>Copy links of selected (Neko Links)</source>\n        <translation>批量复制选中项目的分享链接 (Neko Links)</translation>\n    </message>\n    <message>\n        <source>Allow other devices to connect</source>\n        <translation>允许其他设备连接</translation>\n    </message>\n    <message>\n        <source>Resolve domain</source>\n        <translation>将服务器域名解析为 IP</translation>\n    </message>\n    <message>\n        <source>Resolving domain to IP, if support.</source>\n        <translation>将服务器域名解析为 IP（如果支持）。</translation>\n    </message>\n    <message>\n        <source>Export %1 config</source>\n        <translation>导出 %1 配置</translation>\n    </message>\n    <message>\n        <source>Routing Settings</source>\n        <translation>路由设置</translation>\n    </message>\n    <message>\n        <source>Tun Settings</source>\n        <translation>Tun 设置</translation>\n    </message>\n    <message>\n        <source>Restart Program</source>\n        <translation>重启程序</translation>\n    </message>\n    <message>\n        <source>Not Running</source>\n        <translation>未启动</translation>\n    </message>\n    <message>\n        <source>Current server is incompatible with Tun. Please stop the server first, enable Tun Mode, and then restart.</source>\n        <translation>当前服务器与 Tun 不兼容。请先停止服务器，打开 Tun 模式后再启动。</translation>\n    </message>\n    <message>\n        <source>Open Config Folder</source>\n        <translation>打开配置目录</translation>\n    </message>\n    <message>\n        <source>Set ignore keyword</source>\n        <translation>设置忽略关键字</translation>\n    </message>\n    <message>\n        <source>Set the following keywords to ignore?\nSplit by line.</source>\n        <translation>将以下关键字设置为忽略？\n一行一个。</translation>\n    </message>\n    <message>\n        <source>Save as route</source>\n        <translation>保存为路由规则</translation>\n    </message>\n    <message>\n        <source>Save &quot;%1&quot; as a routing rule?</source>\n        <translation>将&quot;%1&quot;保存为一条路由规则？</translation>\n    </message>\n    <message>\n        <source>Edit</source>\n        <translation>编辑</translation>\n    </message>\n    <message>\n        <source>Current Select</source>\n        <translation>当前选中</translation>\n    </message>\n    <message>\n        <source>Show Window</source>\n        <translation>显示主窗口</translation>\n    </message>\n    <message>\n        <source>Settings changed</source>\n        <translation>设置改变</translation>\n    </message>\n    <message>\n        <source>Restart the program to take effect.</source>\n        <translation>重启程序生效。</translation>\n    </message>\n    <message>\n        <source>Please run NekoBox as admin</source>\n        <translation>请以管理员权限运行 NekoBox</translation>\n    </message>\n    <message>\n        <source>Restart Proxy</source>\n        <translation>重启代理</translation>\n    </message>\n    <message>\n        <source>Failed to start profile %1</source>\n        <translation>启动配置失败: %1</translation>\n    </message>\n    <message>\n        <source>Failed to stop, please restart the program.</source>\n        <translation>停止失败，请重启程序。</translation>\n    </message>\n    <message>\n        <source>If there is no response for a long time, it is recommended to restart the software.</source>\n        <translation>如果长时间没有反应，建议重启软件。</translation>\n    </message>\n    <message>\n        <source>Select mode, double-click or press Enter to select a profile, press ESC to exit.</source>\n        <translation>选择模式，双击或按回车键选择一个配置文件，按ESC键退出。</translation>\n    </message>\n    <message>\n        <source>Latency</source>\n        <translation>延迟</translation>\n    </message>\n    <message>\n        <source>UDP latency</source>\n        <translation>UDP 延迟</translation>\n    </message>\n    <message>\n        <source>Download speed</source>\n        <translation>下载速度</translation>\n    </message>\n    <message>\n        <source>In and Out IP</source>\n        <translation>入口出口 IP</translation>\n    </message>\n    <message>\n        <source>Test Options</source>\n        <translation>测试选项</translation>\n    </message>\n    <message>\n        <source>Stop Testing</source>\n        <translation>停止测试</translation>\n    </message>\n    <message>\n        <source>URL Test</source>\n        <translation>URL 测试</translation>\n    </message>\n</context>\n<context>\n    <name>ProxyItem</name>\n    <message>\n        <source>Confirmation</source>\n        <translation>确认</translation>\n    </message>\n    <message>\n        <source>Remove %1?</source>\n        <translation>删除 %1 ?</translation>\n    </message>\n</context>\n<context>\n    <name>QGuiApplication</name>\n    <message>\n        <source>QT_LAYOUT_DIRECTION</source>\n        <translation>LTR</translation>\n    </message>\n</context>\n<context>\n    <name>QObject</name>\n    <message>\n        <source>As link</source>\n        <translation>作为链接</translation>\n    </message>\n    <message>\n        <source>url detected</source>\n        <translation>检测到 URL</translation>\n    </message>\n    <message>\n        <source>%1\nHow to update?</source>\n        <translation>%1\n如何处理？</translation>\n    </message>\n    <message>\n        <source>Added %1 profiles:\n%2\nDeleted %3 Profiles:\n%4</source>\n        <translation>增加了 %1 个配置：\n%2\n删除了 %3 个配置：\n%4</translation>\n    </message>\n    <message>\n        <source>Proxy: %1\nDirect: %2</source>\n        <translation>代理: %1\n直连: %2</translation>\n    </message>\n    <message>\n        <source>Used: %1 Remain: %2 Expire: %3</source>\n        <translation>已用 %1 剩余 %2 过期 %3</translation>\n    </message>\n    <message>\n        <source>Core not found: %1</source>\n        <translation>找不到 &quot;%1&quot; 核心。请前往设置</translation>\n    </message>\n    <message>\n        <source>Update</source>\n        <translation>更新</translation>\n    </message>\n    <message>\n        <source>No update</source>\n        <translation>无更新</translation>\n    </message>\n    <message>\n        <source>Open in browser</source>\n        <translation>浏览器打开</translation>\n    </message>\n    <message>\n        <source>Close</source>\n        <translation>关闭</translation>\n    </message>\n    <message>\n        <source>Update is ready, restart to install?</source>\n        <translation>更新已下载好，重启应用？</translation>\n    </message>\n    <message>\n        <source>Update found: %1\nRelease note:\n%2</source>\n        <translation>检测到更新: %1\n更新日志:\n%2</translation>\n    </message>\n    <message>\n        <source>Unavailable</source>\n        <translation>不可用</translation>\n    </message>\n    <message>\n        <source>Request with proxy but no profile started.</source>\n        <translation>即将使用代理请求，但是代理未启动。</translation>\n    </message>\n    <message>\n        <source>Chain Proxy</source>\n        <translation>链式代理</translation>\n    </message>\n    <message>\n        <source>Requesting subscription: %1</source>\n        <translation>正在请求订阅: %1</translation>\n    </message>\n    <message>\n        <source>Requesting subscription %1 error: %2</source>\n        <translation>请求订阅 %1 错误: %2</translation>\n    </message>\n    <message>\n        <source>Nothing</source>\n        <translation>无</translation>\n    </message>\n    <message>\n        <source>Change of %1:</source>\n        <translation>%1 变化:</translation>\n    </message>\n    <message>\n        <source>Select</source>\n        <translation>选择</translation>\n    </message>\n    <message>\n        <source>Clearing servers...</source>\n        <translation>正在清理服务器...</translation>\n    </message>\n    <message>\n        <source>Subscription request fininshed: %1</source>\n        <translation>订阅请求完成: %1</translation>\n    </message>\n    <message>\n        <source>Core exited, restarting.</source>\n        <translation>Core 退出，正在重新启动。</translation>\n    </message>\n    <message>\n        <source>Core exits too frequently, stop automatic restart this profile.</source>\n        <translation>Core 退出太频繁，停止自动重启。</translation>\n    </message>\n    <message>\n        <source>As Subscription (create new group)</source>\n        <translation>作为订阅（创建新组）</translation>\n    </message>\n    <message>\n        <source>As Subscription (add to this group)</source>\n        <translation>作为订阅（添加到该组）</translation>\n    </message>\n    <message>\n        <source>Default</source>\n        <translation>默认</translation>\n    </message>\n    <message>\n        <source>The last speed test did not exit completely, please wait. If it persists, please restart the program.</source>\n        <translation>上次速度测试未完全退出，请等待。如果问题仍然存在，请重新启动程序。</translation>\n    </message>\n</context>\n<context>\n    <name>Qv2ray::ui::widgets::AutoCompleteTextEdit</name>\n    <message>\n        <source>You can not input space characters here.</source>\n        <translation></translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "ui/GroupSort.hpp",
    "content": "#pragma once\n\n// implement in mainwindow\nnamespace GroupSortMethod {\n    enum GroupSortMethod {\n        Raw,\n        ByType,\n        ByAddress,\n        ByName,\n        ByLatency,\n        ById,\n    };\n}\n\nstruct GroupSortAction {\n    GroupSortMethod::GroupSortMethod method = GroupSortMethod::Raw;\n    bool save_sort = false;  // 保存到文件\n    bool descending = false; // 默认升序，开这个就是降序\n    bool scroll_to_started = false;\n};\n"
  },
  {
    "path": "ui/Icon.cpp",
    "content": "#include \"Icon.hpp\"\n\n#include \"main/NekoGui.hpp\"\n\n#include <QPainter>\n\nQPixmap Icon::GetTrayIcon(Icon::TrayIconStatus status) {\n    QPixmap pixmap;\n\n    // software embedded icon\n    auto pixmap_read = QPixmap(\":/neko/\" + software_name.toLower() + \".png\");\n    if (!pixmap_read.isNull()) pixmap = pixmap_read;\n\n    // software pack icon\n    pixmap_read = QPixmap(\"../\" + software_name.toLower() + \".png\");\n    if (!pixmap_read.isNull()) pixmap = pixmap_read;\n\n    // user icon\n    pixmap_read = QPixmap(\"./\" + software_name.toLower() + \".png\");\n    if (!pixmap_read.isNull()) pixmap = pixmap_read;\n\n    if (status == TrayIconStatus::NONE) return pixmap;\n\n    auto p = QPainter(&pixmap);\n\n    auto side = pixmap.width();\n    auto radius = side * 0.4;\n    auto d = side * 0.3;\n    auto margin = side * 0.05;\n\n    if (status == TrayIconStatus::RUNNING) {\n        p.setBrush(QBrush(Qt::darkGreen));\n    } else if (status == TrayIconStatus::SYSTEM_PROXY) {\n        p.setBrush(QBrush(Qt::blue));\n    } else if (status == TrayIconStatus::VPN) {\n        p.setBrush(QBrush(Qt::red));\n    }\n    p.drawRoundedRect(\n        QRect(side - d - margin,\n              side - d - margin,\n              d,\n              d),\n        radius,\n        radius);\n    p.end();\n\n    return pixmap;\n}\n\nQPixmap Icon::GetMaterialIcon(const QString &name) {\n    QPixmap pixmap(\":/icon/material/\" + name + \".svg\");\n    return pixmap;\n}\n"
  },
  {
    "path": "ui/Icon.hpp",
    "content": "#pragma once\n\n#include <QPixmap>\n\nnamespace Icon {\n\n    enum TrayIconStatus {\n        NONE,\n        RUNNING,\n        SYSTEM_PROXY,\n        VPN,\n    };\n\n    QPixmap GetTrayIcon(TrayIconStatus status);\n\n    QPixmap GetMaterialIcon(const QString &name);\n\n} // namespace Icon\n"
  },
  {
    "path": "ui/ThemeManager.cpp",
    "content": "#include <QStyle>\n#include <QApplication>\n#include <QStyleFactory>\n\n#include \"ThemeManager.hpp\"\n\nThemeManager *themeManager = new ThemeManager;\n\nextern QString ReadFileText(const QString &path);\n\nvoid ThemeManager::ApplyTheme(const QString &theme) {\n    auto internal = [=] {\n        if (this->system_style_name.isEmpty()) {\n            this->system_style_name = qApp->style()->objectName();\n        }\n        if (this->current_theme == theme) {\n            return;\n        }\n\n        bool ok;\n        auto themeId = theme.toInt(&ok);\n\n        if (ok) {\n            // System & Built-in\n            QString qss;\n\n            if (themeId != 0) {\n                QString path;\n                std::map<QString, QString> replace;\n                switch (themeId) {\n                    case 1:\n                        path = \":/themes/feiyangqingyun/qss/flatgray.css\";\n                        replace[\":/qss/\"] = \":/themes/feiyangqingyun/qss/\";\n                        break;\n                    case 2:\n                        path = \":/themes/feiyangqingyun/qss/lightblue.css\";\n                        replace[\":/qss/\"] = \":/themes/feiyangqingyun/qss/\";\n                        break;\n                    case 3:\n                        path = \":/themes/feiyangqingyun/qss/blacksoft.css\";\n                        replace[\":/qss/\"] = \":/themes/feiyangqingyun/qss/\";\n                        break;\n                    default:\n                        return;\n                }\n                qss = ReadFileText(path);\n                for (auto const &[a, b]: replace) {\n                    qss = qss.replace(a, b);\n                }\n            }\n\n            auto system_style = QStyleFactory::create(this->system_style_name);\n\n            if (themeId == 0) {\n                // system theme\n                qApp->setPalette(system_style->standardPalette());\n                qApp->setStyle(system_style);\n                qApp->setStyleSheet(\"\");\n            } else {\n                if (themeId == 1 || themeId == 2 || themeId == 3) {\n                    // feiyangqingyun theme\n                    QString paletteColor = qss.mid(20, 7);\n                    qApp->setPalette(QPalette(paletteColor));\n                } else {\n                    // other theme\n                    qApp->setPalette(system_style->standardPalette());\n                }\n                qApp->setStyleSheet(qss);\n            }\n        } else {\n            // QStyleFactory\n            const auto &_style = QStyleFactory::create(theme);\n            if (_style != nullptr) {\n                qApp->setPalette(_style->standardPalette());\n                qApp->setStyle(_style);\n                qApp->setStyleSheet(\"\");\n            }\n        }\n\n        current_theme = theme;\n    };\n    internal();\n\n    auto nekoray_css = ReadFileText(\":/neko/neko.css\");\n    qApp->setStyleSheet(qApp->styleSheet().append(\"\\n\").append(nekoray_css));\n}\n"
  },
  {
    "path": "ui/ThemeManager.hpp",
    "content": "#pragma once\n\nclass ThemeManager {\npublic:\n    QString system_style_name = \"\";\n    QString current_theme = \"0\"; // int: 0:system 1+:builtin string: QStyleFactory\n\n    void ApplyTheme(const QString &theme);\n};\n\nextern ThemeManager *themeManager;\n"
  },
  {
    "path": "ui/dialog_basic_settings.cpp",
    "content": "#include \"dialog_basic_settings.h\"\n#include \"ui_dialog_basic_settings.h\"\n\n#include \"3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp\"\n#include \"fmt/Preset.hpp\"\n#include \"ui/ThemeManager.hpp\"\n#include \"ui/Icon.hpp\"\n#include \"main/GuiUtils.hpp\"\n#include \"main/NekoGui.hpp\"\n\n#include <QStyleFactory>\n#include <QFileDialog>\n#include <QInputDialog>\n#include <QMessageBox>\n#include <QTimer>\n\nclass ExtraCoreWidget : public QWidget {\npublic:\n    QString coreName;\n\n    QLabel *label_name;\n    MyLineEdit *lineEdit_path;\n    QPushButton *pushButton_pick;\n\n    explicit ExtraCoreWidget(QJsonObject *extraCore, const QString &coreName_,\n                             QWidget *parent = nullptr)\n        : QWidget(parent) {\n        coreName = coreName_;\n        label_name = new QLabel;\n        label_name->setText(coreName);\n        lineEdit_path = new MyLineEdit;\n        lineEdit_path->setText(extraCore->value(coreName).toString());\n        pushButton_pick = new QPushButton;\n        pushButton_pick->setText(QObject::tr(\"Select\"));\n        auto layout = new QHBoxLayout;\n        layout->addWidget(label_name);\n        layout->addWidget(lineEdit_path);\n        layout->addWidget(pushButton_pick);\n        setLayout(layout);\n        setContentsMargins(0, 0, 0, 0);\n        //\n        connect(pushButton_pick, &QPushButton::clicked, this, [=] {\n            auto fn = QFileDialog::getOpenFileName(this, QObject::tr(\"Select\"), QDir::currentPath(),\n                                                   \"\", nullptr, QFileDialog::Option::ReadOnly);\n            if (!fn.isEmpty()) {\n                lineEdit_path->setText(fn);\n            }\n        });\n        connect(lineEdit_path, &QLineEdit::textChanged, this, [=](const QString &newTxt) {\n            extraCore->insert(coreName, newTxt);\n        });\n    }\n};\n\nDialogBasicSettings::DialogBasicSettings(QWidget *parent)\n    : QDialog(parent), ui(new Ui::DialogBasicSettings) {\n    ui->setupUi(this);\n    ADD_ASTERISK(this);\n\n    // Common\n\n    ui->log_level->addItems(QStringLiteral(\"trace debug info warn error fatal panic\").split(\" \"));\n    ui->mux_protocol->addItems({\"h2mux\", \"smux\", \"yamux\"});\n\n    refresh_auth();\n\n    D_LOAD_STRING(inbound_address)\n    D_LOAD_COMBO_STRING(log_level)\n    CACHE.custom_inbound = NekoGui::dataStore->custom_inbound;\n    D_LOAD_INT(inbound_socks_port)\n    D_LOAD_INT(test_concurrent)\n    D_LOAD_INT(test_download_timeout)\n    D_LOAD_STRING(test_latency_url)\n    D_LOAD_STRING(test_download_url)\n    D_LOAD_BOOL(old_share_link_format)\n\n    connect(ui->custom_inbound_edit, &QPushButton::clicked, this, [=] {\n        C_EDIT_JSON_ALLOW_EMPTY(custom_inbound)\n    });\n\n#ifdef Q_OS_WIN\n    connect(ui->sys_proxy_format, &QPushButton::clicked, this, [=] {\n        bool ok;\n        auto str = QInputDialog::getItem(this, ui->sys_proxy_format->text() + \" (Windows)\",\n                                         tr(\"Advanced system proxy settings. Please select a format.\"),\n                                         Preset::Windows::system_proxy_format,\n                                         Preset::Windows::system_proxy_format.indexOf(NekoGui::dataStore->system_proxy_format),\n                                         false, &ok);\n        if (ok) NekoGui::dataStore->system_proxy_format = str;\n    });\n#else\n    ui->sys_proxy_format->hide();\n#endif\n\n    // Style\n    ui->connection_statistics_box->setDisabled(true);\n    //\n    D_LOAD_BOOL(check_include_pre)\n    D_LOAD_BOOL(connection_statistics)\n    D_LOAD_BOOL(start_minimal)\n    D_LOAD_INT(max_log_line)\n    //\n    if (NekoGui::dataStore->traffic_loop_interval == 500) {\n        ui->rfsh_r->setCurrentIndex(0);\n    } else if (NekoGui::dataStore->traffic_loop_interval == 1000) {\n        ui->rfsh_r->setCurrentIndex(1);\n    } else if (NekoGui::dataStore->traffic_loop_interval == 2000) {\n        ui->rfsh_r->setCurrentIndex(2);\n    } else if (NekoGui::dataStore->traffic_loop_interval == 3000) {\n        ui->rfsh_r->setCurrentIndex(3);\n    } else if (NekoGui::dataStore->traffic_loop_interval == 5000) {\n        ui->rfsh_r->setCurrentIndex(4);\n    } else {\n        ui->rfsh_r->setCurrentIndex(5);\n    }\n    //\n    ui->language->setCurrentIndex(NekoGui::dataStore->language);\n    connect(ui->language, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) {\n        CACHE.needRestart = true;\n    });\n    //\n    int built_in_len = ui->theme->count();\n    ui->theme->addItems(QStyleFactory::keys());\n    //\n    bool ok;\n    auto themeId = NekoGui::dataStore->theme.toInt(&ok);\n    if (ok) {\n        ui->theme->setCurrentIndex(themeId);\n    } else {\n        ui->theme->setCurrentText(NekoGui::dataStore->theme);\n    }\n    //\n    connect(ui->theme, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) {\n        if (index + 1 <= built_in_len) {\n            themeManager->ApplyTheme(Int2String(index));\n            NekoGui::dataStore->theme = Int2String(index);\n        } else {\n            themeManager->ApplyTheme(ui->theme->currentText());\n            NekoGui::dataStore->theme = ui->theme->currentText();\n        }\n        repaint();\n        mainwindow->repaint();\n        NekoGui::dataStore->Save();\n    });\n\n    // Subscription\n\n    ui->user_agent->setText(NekoGui::dataStore->user_agent);\n    ui->user_agent->setPlaceholderText(NekoGui::dataStore->GetUserAgent(true));\n    D_LOAD_BOOL(sub_use_proxy)\n    D_LOAD_BOOL(sub_clear)\n    D_LOAD_BOOL(sub_insecure)\n    D_LOAD_INT_ENABLE(sub_auto_update, sub_auto_update_enable)\n\n    // Core\n\n    ui->groupBox_core->setTitle(software_core_name);\n    //\n    CACHE.extraCore = QString2QJsonObject(NekoGui::dataStore->extraCore->core_map);\n    if (!CACHE.extraCore.contains(\"naive\")) CACHE.extraCore.insert(\"naive\", \"\");\n    if (!CACHE.extraCore.contains(\"hysteria2\")) CACHE.extraCore.insert(\"hysteria2\", \"\");\n    if (!CACHE.extraCore.contains(\"tuic\")) CACHE.extraCore.insert(\"tuic\", \"\");\n    //\n    auto extra_core_layout = ui->extra_core_box_scrollAreaWidgetContents->layout();\n    for (const auto &s: CACHE.extraCore.keys()) {\n        extra_core_layout->addWidget(new ExtraCoreWidget(&CACHE.extraCore, s));\n    }\n    //\n    connect(ui->extra_core_add, &QPushButton::clicked, this, [=] {\n        bool ok;\n        auto s = QInputDialog::getText(nullptr, tr(\"Add\"),\n                                       tr(\"Please input the core name.\"),\n                                       QLineEdit::Normal, \"\", &ok)\n                     .trimmed();\n        if (s.isEmpty() || !ok) return;\n        if (CACHE.extraCore.contains(s)) return;\n        extra_core_layout->addWidget(new ExtraCoreWidget(&CACHE.extraCore, s));\n        CACHE.extraCore.insert(s, \"\");\n    });\n    connect(ui->extra_core_del, &QPushButton::clicked, this, [=] {\n        bool ok;\n        auto s = QInputDialog::getItem(nullptr, tr(\"Delete\"),\n                                       tr(\"Please select the core name.\"),\n                                       CACHE.extraCore.keys(), 0, false, &ok);\n        if (s.isEmpty() || !ok) return;\n        for (int i = 0; i < extra_core_layout->count(); i++) {\n            auto item = extra_core_layout->itemAt(i);\n            auto ecw = dynamic_cast<ExtraCoreWidget *>(item->widget());\n            if (ecw != nullptr && ecw->coreName == s) {\n                ecw->deleteLater();\n                CACHE.extraCore.remove(s);\n                return;\n            }\n        }\n    });\n\n    // Mux\n    D_LOAD_INT(mux_concurrency)\n    D_LOAD_COMBO_STRING(mux_protocol)\n    D_LOAD_BOOL(mux_padding)\n    D_LOAD_BOOL(mux_default_on)\n\n    // Security\n\n    ui->utlsFingerprint->addItems(Preset::SingBox::UtlsFingerPrint);\n\n    D_LOAD_BOOL(skip_cert)\n    ui->utlsFingerprint->setCurrentText(NekoGui::dataStore->utlsFingerprint);\n}\n\nDialogBasicSettings::~DialogBasicSettings() {\n    delete ui;\n}\n\nvoid DialogBasicSettings::accept() {\n    // Common\n\n    D_SAVE_STRING(inbound_address)\n    D_SAVE_COMBO_STRING(log_level)\n    NekoGui::dataStore->custom_inbound = CACHE.custom_inbound;\n    D_SAVE_INT(inbound_socks_port)\n    D_SAVE_INT(test_concurrent)\n    D_SAVE_INT(test_download_timeout)\n    D_SAVE_STRING(test_latency_url)\n    D_SAVE_STRING(test_download_url)\n    D_SAVE_BOOL(old_share_link_format)\n\n    // Style\n\n    NekoGui::dataStore->language = ui->language->currentIndex();\n    D_SAVE_BOOL(connection_statistics)\n    D_SAVE_BOOL(check_include_pre)\n    D_SAVE_BOOL(start_minimal)\n    D_SAVE_INT(max_log_line)\n\n    if (NekoGui::dataStore->max_log_line <= 0) {\n        NekoGui::dataStore->max_log_line = 200;\n    }\n\n    if (ui->rfsh_r->currentIndex() == 0) {\n        NekoGui::dataStore->traffic_loop_interval = 500;\n    } else if (ui->rfsh_r->currentIndex() == 1) {\n        NekoGui::dataStore->traffic_loop_interval = 1000;\n    } else if (ui->rfsh_r->currentIndex() == 2) {\n        NekoGui::dataStore->traffic_loop_interval = 2000;\n    } else if (ui->rfsh_r->currentIndex() == 3) {\n        NekoGui::dataStore->traffic_loop_interval = 3000;\n    } else if (ui->rfsh_r->currentIndex() == 4) {\n        NekoGui::dataStore->traffic_loop_interval = 5000;\n    } else {\n        NekoGui::dataStore->traffic_loop_interval = 0;\n    }\n\n    // Subscription\n\n    if (ui->sub_auto_update_enable->isChecked()) {\n        TM_auto_update_subsctiption_Reset_Minute(ui->sub_auto_update->text().toInt());\n    } else {\n        TM_auto_update_subsctiption_Reset_Minute(0);\n    }\n\n    NekoGui::dataStore->user_agent = ui->user_agent->text();\n    D_SAVE_BOOL(sub_use_proxy)\n    D_SAVE_BOOL(sub_clear)\n    D_SAVE_BOOL(sub_insecure)\n    D_SAVE_INT_ENABLE(sub_auto_update, sub_auto_update_enable)\n\n    // Core\n\n    NekoGui::dataStore->extraCore->core_map = QJsonObject2QString(CACHE.extraCore, true);\n\n    // Mux\n    D_SAVE_INT(mux_concurrency)\n    D_SAVE_COMBO_STRING(mux_protocol)\n    D_SAVE_BOOL(mux_padding)\n    D_SAVE_BOOL(mux_default_on)\n\n    // Security\n\n    D_SAVE_BOOL(skip_cert)\n    NekoGui::dataStore->utlsFingerprint = ui->utlsFingerprint->currentText();\n\n    // 关闭连接统计，停止刷新前清空记录。\n    if (NekoGui::dataStore->traffic_loop_interval == 0 || !NekoGui::dataStore->connection_statistics) {\n        MW_dialog_message(\"\", \"ClearConnectionList\");\n    }\n\n    QStringList str{\"UpdateDataStore\"};\n    if (CACHE.needRestart) str << \"NeedRestart\";\n    MW_dialog_message(Dialog_DialogBasicSettings, str.join(\",\"));\n    QDialog::accept();\n}\n\n// slots\n\nvoid DialogBasicSettings::refresh_auth() {\n    ui->inbound_auth->setText({});\n    if (NekoGui::dataStore->inbound_auth->NeedAuth()) {\n        ui->inbound_auth->setIcon(Icon::GetMaterialIcon(\"lock-outline\"));\n    } else {\n        ui->inbound_auth->setIcon(Icon::GetMaterialIcon(\"lock-open-outline\"));\n    }\n}\n\nvoid DialogBasicSettings::on_set_custom_icon_clicked() {\n    auto title = ui->set_custom_icon->text();\n    QString user_icon_path = \"./\" + software_name.toLower() + \".png\";\n    auto c = QMessageBox::question(this, title, tr(\"Please select a PNG file.\"),\n                                   tr(\"Select\"), tr(\"Reset\"), tr(\"Cancel\"), 2, 2);\n    if (c == 0) {\n        auto fn = QFileDialog::getOpenFileName(this, QObject::tr(\"Select\"), QDir::currentPath(),\n                                               \"*.png\", nullptr, QFileDialog::Option::ReadOnly);\n        QImage img(fn);\n        if (img.isNull() || img.height() != img.width()) {\n            MessageBoxWarning(title, tr(\"Please select a valid square image.\"));\n            return;\n        }\n        QFile::remove(user_icon_path);\n        QFile::copy(fn, user_icon_path);\n    } else if (c == 1) {\n        QFile::remove(user_icon_path);\n    } else {\n        return;\n    }\n    MW_dialog_message(Dialog_DialogBasicSettings, \"UpdateIcon\");\n}\n\nvoid DialogBasicSettings::on_inbound_auth_clicked() {\n    auto w = new QDialog(this);\n    w->setWindowTitle(tr(\"Inbound Auth\"));\n    auto layout = new QGridLayout;\n    w->setLayout(layout);\n    //\n    auto user_l = new QLabel(tr(\"Username\"));\n    auto pass_l = new QLabel(tr(\"Password\"));\n    auto user = new MyLineEdit;\n    auto pass = new MyLineEdit;\n    user->setText(NekoGui::dataStore->inbound_auth->username);\n    pass->setText(NekoGui::dataStore->inbound_auth->password);\n    //\n    layout->addWidget(user_l, 0, 0);\n    layout->addWidget(user, 0, 1);\n    layout->addWidget(pass_l, 1, 0);\n    layout->addWidget(pass, 1, 1);\n    auto box = new QDialogButtonBox;\n    box->setOrientation(Qt::Horizontal);\n    box->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);\n    connect(box, &QDialogButtonBox::accepted, w, [=] {\n        NekoGui::dataStore->inbound_auth->username = user->text();\n        NekoGui::dataStore->inbound_auth->password = pass->text();\n        MW_dialog_message(Dialog_DialogBasicSettings, \"UpdateDataStore\");\n        w->accept();\n    });\n    connect(box, &QDialogButtonBox::rejected, w, &QDialog::reject);\n    layout->addWidget(box, 2, 1);\n    //\n    w->exec();\n    w->deleteLater();\n    refresh_auth();\n}\n\nvoid DialogBasicSettings::on_core_settings_clicked() {\n    auto w = new QDialog(this);\n    w->setWindowTitle(software_core_name + \" Core Options\");\n    auto layout = new QGridLayout;\n    w->setLayout(layout);\n    //\n    auto line = -1;\n    QCheckBox *core_box_enable_clash_api;\n    MyLineEdit *core_box_clash_api;\n    MyLineEdit *core_box_clash_api_secret;\n    MyLineEdit *core_box_underlying_dns;\n    //\n    auto core_box_underlying_dns_l = new QLabel(tr(\"Override underlying DNS\"));\n    core_box_underlying_dns = new MyLineEdit;\n    core_box_underlying_dns->setText(NekoGui::dataStore->core_box_underlying_dns);\n    core_box_underlying_dns->setMinimumWidth(300);\n    layout->addWidget(core_box_underlying_dns_l, ++line, 0);\n    layout->addWidget(core_box_underlying_dns, line, 1);\n    //\n    auto core_box_enable_clash_api_l = new QLabel(\"Enable Clash API\");\n    core_box_enable_clash_api = new QCheckBox;\n    core_box_enable_clash_api->setChecked(NekoGui::dataStore->core_box_clash_api > 0);\n    layout->addWidget(core_box_enable_clash_api_l, ++line, 0);\n    layout->addWidget(core_box_enable_clash_api, line, 1);\n    //\n    auto core_box_clash_api_l = new QLabel(\"Clash API Listen Port\");\n    core_box_clash_api = new MyLineEdit;\n    core_box_clash_api->setText(Int2String(std::abs(NekoGui::dataStore->core_box_clash_api)));\n    layout->addWidget(core_box_clash_api_l, ++line, 0);\n    layout->addWidget(core_box_clash_api, line, 1);\n    //\n    auto core_box_clash_api_secret_l = new QLabel(\"Clash API Secret\");\n    core_box_clash_api_secret = new MyLineEdit;\n    core_box_clash_api_secret->setText(NekoGui::dataStore->core_box_clash_api_secret);\n    layout->addWidget(core_box_clash_api_secret_l, ++line, 0);\n    layout->addWidget(core_box_clash_api_secret, line, 1);\n    //\n    auto box = new QDialogButtonBox;\n    box->setOrientation(Qt::Horizontal);\n    box->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);\n    connect(box, &QDialogButtonBox::accepted, w, [=] {\n        NekoGui::dataStore->core_box_underlying_dns = core_box_underlying_dns->text();\n        NekoGui::dataStore->core_box_clash_api = core_box_clash_api->text().toInt() * (core_box_enable_clash_api->isChecked() ? 1 : -1);\n        NekoGui::dataStore->core_box_clash_api_secret = core_box_clash_api_secret->text();\n        MW_dialog_message(Dialog_DialogBasicSettings, \"UpdateDataStore\");\n        w->accept();\n    });\n    connect(box, &QDialogButtonBox::rejected, w, &QDialog::reject);\n    layout->addWidget(box, ++line, 1);\n    //\n    ADD_ASTERISK(w)\n    w->exec();\n    w->deleteLater();\n    refresh_auth();\n}\n"
  },
  {
    "path": "ui/dialog_basic_settings.h",
    "content": "#ifndef DIALOG_BASIC_SETTINGS_H\n#define DIALOG_BASIC_SETTINGS_H\n\n#include <QDialog>\n#include <QJsonObject>\n\nnamespace Ui {\n    class DialogBasicSettings;\n}\n\nclass DialogBasicSettings : public QDialog {\n    Q_OBJECT\n\npublic:\n    explicit DialogBasicSettings(QWidget *parent = nullptr);\n\n    ~DialogBasicSettings();\n\npublic slots:\n\n    void accept();\n\nprivate:\n    Ui::DialogBasicSettings *ui;\n\n    struct {\n        QJsonObject extraCore;\n        QString custom_inbound;\n        bool needRestart = false;\n    } CACHE;\n\nprivate slots:\n\n    void refresh_auth();\n\n    void on_set_custom_icon_clicked();\n\n    void on_inbound_auth_clicked();\n\n    void on_core_settings_clicked();\n};\n\n#endif // DIALOG_BASIC_SETTINGS_H\n"
  },
  {
    "path": "ui/dialog_basic_settings.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>DialogBasicSettings</class>\n <widget class=\"QDialog\" name=\"DialogBasicSettings\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>600</width>\n    <height>400</height>\n   </rect>\n  </property>\n  <property name=\"sizePolicy\">\n   <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n    <horstretch>0</horstretch>\n    <verstretch>0</verstretch>\n   </sizepolicy>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Basic Settings</string>\n  </property>\n  <layout class=\"QGridLayout\" name=\"gridLayout\">\n   <item row=\"8\" column=\"3\">\n    <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n     <property name=\"orientation\">\n      <enum>Qt::Horizontal</enum>\n     </property>\n     <property name=\"standardButtons\">\n      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>\n     </property>\n    </widget>\n   </item>\n   <item row=\"2\" column=\"3\">\n    <widget class=\"QTabWidget\" name=\"tabWidget\">\n     <property name=\"currentIndex\">\n      <number>0</number>\n     </property>\n     <widget class=\"QWidget\" name=\"tab_1\">\n      <attribute name=\"title\">\n       <string>Common</string>\n      </attribute>\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n       <item>\n        <layout class=\"QHBoxLayout\" name=\"hlayout_l1\">\n         <item>\n          <widget class=\"QGroupBox\" name=\"horizontalGroupBox_3\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_19\">\n            <item>\n             <widget class=\"QLabel\" name=\"label\">\n              <property name=\"text\">\n               <string>Listen Address</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QLineEdit\" name=\"inbound_address\"/>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"inbound_auth\">\n              <property name=\"text\">\n               <string notr=\"true\"/>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QGroupBox\" name=\"groupbox_custom_inbound\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_10\">\n            <item>\n             <widget class=\"QLabel\" name=\"label_11\">\n              <property name=\"text\">\n               <string>Custom Inbound</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"custom_inbound_edit\">\n              <property name=\"text\">\n               <string>Edit</string>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n        </layout>\n       </item>\n       <item>\n        <layout class=\"QHBoxLayout\" name=\"hlayout_l2\">\n         <item>\n          <widget class=\"QGroupBox\" name=\"horizontalGroupBox_2\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_13\">\n            <item>\n             <widget class=\"QLabel\" name=\"inbound_socks_port_l\">\n              <property name=\"text\">\n               <string>Mixed (SOCKS+HTTP) Listen Port</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QLineEdit\" name=\"inbound_socks_port\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Fixed\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n        </layout>\n       </item>\n       <item>\n        <widget class=\"QGroupBox\" name=\"groupBox1\">\n         <layout class=\"QHBoxLayout\" name=\"horizontalLayout_9\" stretch=\"0,8,1,1\">\n          <item>\n           <widget class=\"QLabel\" name=\"label_13\">\n            <property name=\"text\">\n             <string>Latency Test URL</string>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QLineEdit\" name=\"test_latency_url\"/>\n          </item>\n          <item>\n           <widget class=\"QLabel\" name=\"label_14\">\n            <property name=\"text\">\n             <string>Concurrent</string>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QLineEdit\" name=\"test_concurrent\"/>\n          </item>\n         </layout>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"QGroupBox\" name=\"horizontalGroupBox\">\n         <layout class=\"QHBoxLayout\" name=\"horizontalLayout_12\" stretch=\"0,8,1,1\">\n          <item>\n           <widget class=\"QLabel\" name=\"label_19\">\n            <property name=\"text\">\n             <string>Download Test URL</string>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"MyLineEdit\" name=\"test_download_url\"/>\n          </item>\n          <item>\n           <widget class=\"QLabel\" name=\"label_10\">\n            <property name=\"text\">\n             <string>Timeout (s)</string>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QLineEdit\" name=\"test_download_timeout\"/>\n          </item>\n         </layout>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"QGroupBox\" name=\"horizontalGroupBox1\">\n         <layout class=\"QHBoxLayout\" name=\"horizontalLayout_18\">\n          <item>\n           <widget class=\"QCheckBox\" name=\"check_include_pre\">\n            <property name=\"text\">\n             <string>Include Pre-release when checking update</string>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"Line\" name=\"sys_proxy_format_vline\">\n            <property name=\"orientation\">\n             <enum>Qt::Vertical</enum>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QCheckBox\" name=\"old_share_link_format\">\n            <property name=\"toolTip\">\n             <string>Share VMess Link with v2rayN Format</string>\n            </property>\n            <property name=\"text\">\n             <string>Old Share Link Format</string>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QPushButton\" name=\"sys_proxy_format\">\n            <property name=\"text\">\n             <string>System proxy format</string>\n            </property>\n           </widget>\n          </item>\n         </layout>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"QWidget\" name=\"horizontalWidget_1\" native=\"true\">\n         <property name=\"sizePolicy\">\n          <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n           <horstretch>0</horstretch>\n           <verstretch>0</verstretch>\n          </sizepolicy>\n         </property>\n         <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n          <property name=\"leftMargin\">\n           <number>0</number>\n          </property>\n          <property name=\"topMargin\">\n           <number>0</number>\n          </property>\n          <property name=\"rightMargin\">\n           <number>0</number>\n          </property>\n          <property name=\"bottomMargin\">\n           <number>0</number>\n          </property>\n         </layout>\n        </widget>\n       </item>\n      </layout>\n     </widget>\n     <widget class=\"QWidget\" name=\"tab_2\">\n      <attribute name=\"title\">\n       <string>Style</string>\n      </attribute>\n      <layout class=\"QGridLayout\" name=\"gridLayout_5\">\n       <item row=\"0\" column=\"0\">\n        <layout class=\"QHBoxLayout\" name=\"style_h_1\">\n         <item>\n          <widget class=\"QGroupBox\" name=\"groupBox\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_4\">\n            <item>\n             <widget class=\"QLabel\" name=\"label_8\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Maximum\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>Theme</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QComboBox\" name=\"theme\">\n              <item>\n               <property name=\"text\">\n                <string>System</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">flatgray</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">lightblue</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">blacksoft</string>\n               </property>\n              </item>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"set_custom_icon\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Fixed\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>Set custom icon</string>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QGroupBox\" name=\"groupBox2\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_7\">\n            <item>\n             <widget class=\"QLabel\" name=\"label_15\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Maximum\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string notr=\"true\">Language</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QComboBox\" name=\"language\">\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">System</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">English</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">简体中文</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">فارسی</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">Русский</string>\n               </property>\n              </item>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n        </layout>\n       </item>\n       <item row=\"2\" column=\"0\">\n        <layout class=\"QHBoxLayout\" name=\"style_h_2\">\n         <item>\n          <widget class=\"QGroupBox\" name=\"traffic_statistics_box\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\n            <item>\n             <widget class=\"QLabel\" name=\"label_16\">\n              <property name=\"text\">\n               <string>Statistics refresh rate</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QComboBox\" name=\"rfsh_r\">\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">500ms</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">1s</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">2s</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">3s</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">5s</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string>Off</string>\n               </property>\n              </item>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QGroupBox\" name=\"connection_statistics_box\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_15\">\n            <item>\n             <widget class=\"QLabel\" name=\"label_9\">\n              <property name=\"text\">\n               <string>Connection statistics</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QCheckBox\" name=\"connection_statistics\">\n              <property name=\"text\">\n               <string>Enable</string>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n        </layout>\n       </item>\n       <item row=\"3\" column=\"0\">\n        <layout class=\"QHBoxLayout\" name=\"style_h_3\">\n         <item>\n          <widget class=\"QGroupBox\" name=\"horizontalGroupBox_5\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_6\">\n            <item>\n             <widget class=\"QCheckBox\" name=\"start_minimal\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Fixed\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>Hide dashboard at startup</string>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QGroupBox\" name=\"groupBox_2\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_23\">\n            <item>\n             <widget class=\"QLabel\" name=\"label_17\">\n              <property name=\"text\">\n               <string>Max log lines</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QLineEdit\" name=\"max_log_line\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Ignored\" vsizetype=\"Fixed\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n        </layout>\n       </item>\n      </layout>\n     </widget>\n     <widget class=\"QWidget\" name=\"tab_3\">\n      <attribute name=\"title\">\n       <string>Subscription</string>\n      </attribute>\n      <layout class=\"QGridLayout\" name=\"gridLayout_3\">\n       <item row=\"0\" column=\"1\">\n        <layout class=\"QHBoxLayout\" name=\"horizontalLayout_5\">\n         <item>\n          <widget class=\"QCheckBox\" name=\"sub_auto_update_enable\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Fixed\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>Enable</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QLabel\" name=\"label_21\">\n           <property name=\"text\">\n            <string>Interval (minute, invalid if less than 30)</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QLineEdit\" name=\"sub_auto_update\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Fixed\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n          </widget>\n         </item>\n        </layout>\n       </item>\n       <item row=\"1\" column=\"1\">\n        <widget class=\"MyLineEdit\" name=\"user_agent\"/>\n       </item>\n       <item row=\"2\" column=\"1\">\n        <widget class=\"QCheckBox\" name=\"sub_use_proxy\">\n         <property name=\"text\">\n          <string>Use proxy when updating subscription</string>\n         </property>\n        </widget>\n       </item>\n       <item row=\"3\" column=\"1\">\n        <widget class=\"QCheckBox\" name=\"sub_insecure\">\n         <property name=\"text\">\n          <string>Ignore TLS errors when updating subscription</string>\n         </property>\n        </widget>\n       </item>\n       <item row=\"4\" column=\"1\">\n        <widget class=\"QCheckBox\" name=\"sub_clear\">\n         <property name=\"text\">\n          <string>Clear servers before updating subscription</string>\n         </property>\n        </widget>\n       </item>\n       <item row=\"0\" column=\"0\">\n        <widget class=\"QLabel\" name=\"label_20\">\n         <property name=\"sizePolicy\">\n          <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n           <horstretch>0</horstretch>\n           <verstretch>0</verstretch>\n          </sizepolicy>\n         </property>\n         <property name=\"text\">\n          <string>Automatic update</string>\n         </property>\n        </widget>\n       </item>\n       <item row=\"1\" column=\"0\">\n        <widget class=\"QLabel\" name=\"label_4\">\n         <property name=\"text\">\n          <string>User Agent</string>\n         </property>\n        </widget>\n       </item>\n      </layout>\n     </widget>\n     <widget class=\"QWidget\" name=\"tab_4\">\n      <attribute name=\"title\">\n       <string>Core</string>\n      </attribute>\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n       <item>\n        <widget class=\"QGroupBox\" name=\"groupBox_core\">\n         <property name=\"sizePolicy\">\n          <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n           <horstretch>0</horstretch>\n           <verstretch>0</verstretch>\n          </sizepolicy>\n         </property>\n         <property name=\"title\">\n          <string notr=\"true\">core_name</string>\n         </property>\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\">\n          <item>\n           <widget class=\"QWidget\" name=\"assest_group\" native=\"true\">\n            <layout class=\"QGridLayout\" name=\"gridLayout_2\">\n             <item row=\"0\" column=\"1\">\n              <widget class=\"QComboBox\" name=\"log_level\">\n               <property name=\"sizePolicy\">\n                <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Fixed\">\n                 <horstretch>0</horstretch>\n                 <verstretch>0</verstretch>\n                </sizepolicy>\n               </property>\n              </widget>\n             </item>\n             <item row=\"1\" column=\"0\">\n              <widget class=\"QLabel\" name=\"label_6\">\n               <property name=\"text\">\n                <string>Multiplex (mux)</string>\n               </property>\n              </widget>\n             </item>\n             <item row=\"0\" column=\"0\">\n              <widget class=\"QLabel\" name=\"label_3\">\n               <property name=\"text\">\n                <string notr=\"true\">Loglevel</string>\n               </property>\n              </widget>\n             </item>\n             <item row=\"1\" column=\"1\">\n              <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n               <item>\n                <widget class=\"QComboBox\" name=\"mux_protocol\"/>\n               </item>\n               <item>\n                <widget class=\"QLabel\" name=\"label_7\">\n                 <property name=\"text\">\n                  <string>concurrency</string>\n                 </property>\n                </widget>\n               </item>\n               <item>\n                <widget class=\"QLineEdit\" name=\"mux_concurrency\"/>\n               </item>\n               <item>\n                <widget class=\"QCheckBox\" name=\"mux_padding\">\n                 <property name=\"text\">\n                  <string notr=\"true\">Padding</string>\n                 </property>\n                </widget>\n               </item>\n               <item>\n                <widget class=\"QCheckBox\" name=\"mux_default_on\">\n                 <property name=\"text\">\n                  <string>Default On</string>\n                 </property>\n                </widget>\n               </item>\n              </layout>\n             </item>\n            </layout>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QPushButton\" name=\"core_settings\">\n            <property name=\"text\">\n             <string>Core Options</string>\n            </property>\n           </widget>\n          </item>\n         </layout>\n        </widget>\n       </item>\n      </layout>\n     </widget>\n     <widget class=\"QWidget\" name=\"tab_6\">\n      <attribute name=\"title\">\n       <string>Extra Core</string>\n      </attribute>\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout_5\">\n       <item>\n        <widget class=\"QScrollArea\" name=\"extra_core_box_scrollArea\">\n         <property name=\"frameShape\">\n          <enum>QFrame::NoFrame</enum>\n         </property>\n         <property name=\"widgetResizable\">\n          <bool>true</bool>\n         </property>\n         <widget class=\"QWidget\" name=\"extra_core_box_scrollAreaWidgetContents\">\n          <property name=\"geometry\">\n           <rect>\n            <x>0</x>\n            <y>0</y>\n            <width>632</width>\n            <height>299</height>\n           </rect>\n          </property>\n          <layout class=\"QVBoxLayout\" name=\"verticalLayout_6\">\n           <item>\n            <widget class=\"QWidget\" name=\"horizontalWidget_4\" native=\"true\">\n             <property name=\"sizePolicy\">\n              <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n               <horstretch>0</horstretch>\n               <verstretch>0</verstretch>\n              </sizepolicy>\n             </property>\n             <layout class=\"QHBoxLayout\" name=\"horizontalLayout_8\">\n              <item>\n               <widget class=\"QPushButton\" name=\"extra_core_add\">\n                <property name=\"text\">\n                 <string>Add</string>\n                </property>\n               </widget>\n              </item>\n              <item>\n               <widget class=\"QPushButton\" name=\"extra_core_del\">\n                <property name=\"text\">\n                 <string>Delete</string>\n                </property>\n               </widget>\n              </item>\n             </layout>\n            </widget>\n           </item>\n          </layout>\n         </widget>\n        </widget>\n       </item>\n      </layout>\n     </widget>\n     <widget class=\"QWidget\" name=\"tab_5\">\n      <attribute name=\"title\">\n       <string>Security</string>\n      </attribute>\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout_4\">\n       <item>\n        <widget class=\"QCheckBox\" name=\"skip_cert\">\n         <property name=\"text\">\n          <string>Skip TLS certificate authentication by default (allowInsecure)</string>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"QGroupBox\" name=\"horizontalGroupBox2\">\n         <property name=\"sizePolicy\">\n          <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n           <horstretch>0</horstretch>\n           <verstretch>0</verstretch>\n          </sizepolicy>\n         </property>\n         <layout class=\"QHBoxLayout\" name=\"horizontalLayout_26\">\n          <item>\n           <widget class=\"QLabel\" name=\"label_18\">\n            <property name=\"text\">\n             <string>Default uTLS Fingerprint</string>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QComboBox\" name=\"utlsFingerprint\">\n            <property name=\"editable\">\n             <bool>true</bool>\n            </property>\n           </widget>\n          </item>\n         </layout>\n        </widget>\n       </item>\n      </layout>\n     </widget>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>MyLineEdit</class>\n   <extends>QLineEdit</extends>\n   <header>ui/widget/MyLineEdit.h</header>\n  </customwidget>\n </customwidgets>\n <resources/>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>accepted()</signal>\n   <receiver>DialogBasicSettings</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>248</x>\n     <y>254</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>157</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>rejected()</signal>\n   <receiver>DialogBasicSettings</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>316</x>\n     <y>260</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>286</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "ui/dialog_hotkey.cpp",
    "content": "#include \"dialog_hotkey.h\"\n#include \"ui_dialog_hotkey.h\"\n\n#include \"ui/mainwindow_interface.h\"\n\nDialogHotkey::DialogHotkey(QWidget *parent) : QDialog(parent), ui(new Ui::DialogHotkey) {\n    ui->setupUi(this);\n    ui->show_mainwindow->setKeySequence(NekoGui::dataStore->hotkey_mainwindow);\n    ui->show_groups->setKeySequence(NekoGui::dataStore->hotkey_group);\n    ui->show_routes->setKeySequence(NekoGui::dataStore->hotkey_route);\n    ui->system_proxy->setKeySequence(NekoGui::dataStore->hotkey_system_proxy_menu);\n    GetMainWindow()->RegisterHotkey(true);\n}\n\nDialogHotkey::~DialogHotkey() {\n    if (result() == QDialog::Accepted) {\n        NekoGui::dataStore->hotkey_mainwindow = ui->show_mainwindow->keySequence().toString();\n        NekoGui::dataStore->hotkey_group = ui->show_groups->keySequence().toString();\n        NekoGui::dataStore->hotkey_route = ui->show_routes->keySequence().toString();\n        NekoGui::dataStore->hotkey_system_proxy_menu = ui->system_proxy->keySequence().toString();\n        NekoGui::dataStore->Save();\n    }\n    GetMainWindow()->RegisterHotkey(false);\n    delete ui;\n}\n"
  },
  {
    "path": "ui/dialog_hotkey.h",
    "content": "#pragma once\n\n#include <QDialog>\n#include \"main/NekoGui.hpp\"\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class DialogHotkey;\n}\nQT_END_NAMESPACE\n\nclass DialogHotkey : public QDialog {\n    Q_OBJECT\n\npublic:\n    explicit DialogHotkey(QWidget *parent = nullptr);\n\n    ~DialogHotkey() override;\n\nprivate:\n    Ui::DialogHotkey *ui;\n};\n"
  },
  {
    "path": "ui/dialog_hotkey.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>DialogHotkey</class>\n <widget class=\"QDialog\" name=\"DialogHotkey\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>400</width>\n    <height>300</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Hotkey</string>\n  </property>\n  <layout class=\"QGridLayout\" name=\"gridLayout\">\n   <item row=\"1\" column=\"1\">\n    <widget class=\"QtExtKeySequenceEdit\" name=\"show_groups\"/>\n   </item>\n   <item row=\"2\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_4\">\n     <property name=\"text\">\n      <string>Show routes</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"2\" column=\"1\">\n    <widget class=\"QtExtKeySequenceEdit\" name=\"show_routes\"/>\n   </item>\n   <item row=\"4\" column=\"1\">\n    <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n     <property name=\"focusPolicy\">\n      <enum>Qt::StrongFocus</enum>\n     </property>\n     <property name=\"standardButtons\">\n      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>\n     </property>\n    </widget>\n   </item>\n   <item row=\"0\" column=\"1\">\n    <widget class=\"QtExtKeySequenceEdit\" name=\"show_mainwindow\"/>\n   </item>\n   <item row=\"1\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_3\">\n     <property name=\"text\">\n      <string>Show groups</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"0\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label\">\n     <property name=\"text\">\n      <string>Trigger main window</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"3\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_2\">\n     <property name=\"text\">\n      <string>System Proxy</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"3\" column=\"1\">\n    <widget class=\"QtExtKeySequenceEdit\" name=\"system_proxy\"/>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>QtExtKeySequenceEdit</class>\n   <extends>QKeySequenceEdit</extends>\n   <header>3rdparty/QtExtKeySequenceEdit.h</header>\n  </customwidget>\n </customwidgets>\n <tabstops>\n  <tabstop>show_mainwindow</tabstop>\n  <tabstop>show_groups</tabstop>\n  <tabstop>show_routes</tabstop>\n  <tabstop>system_proxy</tabstop>\n  <tabstop>buttonBox</tabstop>\n </tabstops>\n <resources/>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>accepted()</signal>\n   <receiver>DialogHotkey</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>258</x>\n     <y>255</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>199</x>\n     <y>149</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>rejected()</signal>\n   <receiver>DialogHotkey</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>258</x>\n     <y>255</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>199</x>\n     <y>149</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "ui/dialog_manage_groups.cpp",
    "content": "#include \"dialog_manage_groups.h\"\n#include \"ui_dialog_manage_groups.h\"\n\n#include \"db/Database.hpp\"\n#include \"sub/GroupUpdater.hpp\"\n#include \"main/GuiUtils.hpp\"\n#include \"ui/widget/GroupItem.h\"\n#include \"ui/edit/dialog_edit_group.h\"\n\n#include <QInputDialog>\n#include <QListWidgetItem>\n#include <QMessageBox>\n\n#define AddGroupToListIfExist(_id)                       \\\n    auto __ent = NekoGui::profileManager->GetGroup(_id); \\\n    if (__ent != nullptr) {                              \\\n        auto wI = new QListWidgetItem();                 \\\n        auto w = new GroupItem(this, __ent, wI);         \\\n        wI->setData(114514, _id);                        \\\n        ui->listWidget->addItem(wI);                     \\\n        ui->listWidget->setItemWidget(wI, w);            \\\n    }\n\nDialogManageGroups::DialogManageGroups(QWidget *parent) : QDialog(parent), ui(new Ui::DialogManageGroups) {\n    ui->setupUi(this);\n\n    for (auto id: NekoGui::profileManager->groupsTabOrder) {\n        AddGroupToListIfExist(id)\n    }\n\n    connect(ui->listWidget, &QListWidget::itemDoubleClicked, this, [=](QListWidgetItem *wI) {\n        auto w = dynamic_cast<GroupItem *>(ui->listWidget->itemWidget(wI));\n        emit w->edit_clicked();\n    });\n}\n\nDialogManageGroups::~DialogManageGroups() {\n    delete ui;\n}\n\nvoid DialogManageGroups::on_add_clicked() {\n    auto ent = NekoGui::ProfileManager::NewGroup();\n    auto dialog = new DialogEditGroup(ent, this);\n    int ret = dialog->exec();\n    dialog->deleteLater();\n\n    if (ret == QDialog::Accepted) {\n        NekoGui::profileManager->AddGroup(ent);\n        AddGroupToListIfExist(ent->id);\n        MW_dialog_message(Dialog_DialogManageGroups, \"refresh-1\");\n    }\n}\n\nvoid DialogManageGroups::on_update_all_clicked() {\n    if (QMessageBox::question(this, tr(\"Confirmation\"), tr(\"Update all subscriptions?\")) == QMessageBox::StandardButton::Yes) {\n        UI_update_all_groups();\n    }\n}\n"
  },
  {
    "path": "ui/dialog_manage_groups.h",
    "content": "#pragma once\n\n#include <QWidget>\n#include <QDialog>\n#include <QMenu>\n#include <QTableWidgetItem>\n\n#include \"db/Group.hpp\"\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class DialogManageGroups;\n}\nQT_END_NAMESPACE\n\nclass DialogManageGroups : public QDialog {\n    Q_OBJECT\n\npublic:\n    explicit DialogManageGroups(QWidget *parent = nullptr);\n\n    ~DialogManageGroups() override;\n\nprivate:\n    Ui::DialogManageGroups *ui;\n\nprivate slots:\n\n    void on_add_clicked();\n\n    void on_update_all_clicked();\n};\n"
  },
  {
    "path": "ui/dialog_manage_groups.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>DialogManageGroups</class>\n <widget class=\"QDialog\" name=\"DialogManageGroups\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>640</width>\n    <height>480</height>\n   </rect>\n  </property>\n  <property name=\"focusPolicy\">\n   <enum>Qt::TabFocus</enum>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Groups</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <widget class=\"QListWidget\" name=\"listWidget\">\n     <property name=\"focusPolicy\">\n      <enum>Qt::NoFocus</enum>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <item>\n      <widget class=\"QPushButton\" name=\"add\">\n       <property name=\"focusPolicy\">\n        <enum>Qt::NoFocus</enum>\n       </property>\n       <property name=\"text\">\n        <string>New group</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QPushButton\" name=\"update_all\">\n       <property name=\"focusPolicy\">\n        <enum>Qt::NoFocus</enum>\n       </property>\n       <property name=\"text\">\n        <string>Update all subscriptions</string>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "ui/dialog_manage_routes.cpp",
    "content": "#include \"dialog_manage_routes.h\"\n#include \"ui_dialog_manage_routes.h\"\n\n#include \"3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp\"\n#include \"3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.hpp\"\n#include \"main/GuiUtils.hpp\"\n#include \"fmt/Preset.hpp\"\n\n#include <QFile>\n#include <QMessageBox>\n#include <QListWidget>\n#include <QLineEdit>\n\n#define REFRESH_ACTIVE_ROUTING(name, obj)           \\\n    this->active_routing = name;                    \\\n    setWindowTitle(title_base + \" [\" + name + \"]\"); \\\n    UpdateDisplayRouting(obj, false);\n\nDialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(new Ui::DialogManageRoutes) {\n    ui->setupUi(this);\n    title_base = windowTitle();\n\n    QStringList qsValue = {\"\"};\n    QString dnsHelpDocumentUrl;\n    //\n    ui->outbound_domain_strategy->addItems(Preset::SingBox::DomainStrategy);\n    ui->domainStrategyCombo->addItems(Preset::SingBox::DomainStrategy);\n    qsValue += QStringLiteral(\"prefer_ipv4 prefer_ipv6 ipv4_only ipv6_only\").split(\" \");\n    ui->dns_object->setPlaceholderText(DecodeB64IfValid(\"ewogICJzZXJ2ZXJzIjogW10sCiAgInJ1bGVzIjogW10sCiAgImZpbmFsIjogIiIsCiAgInN0cmF0ZWd5IjogIiIsCiAgImRpc2FibGVfY2FjaGUiOiBmYWxzZSwKICAiZGlzYWJsZV9leHBpcmUiOiBmYWxzZSwKICAiaW5kZXBlbmRlbnRfY2FjaGUiOiBmYWxzZSwKICAicmV2ZXJzZV9tYXBwaW5nIjogZmFsc2UsCiAgImZha2VpcCI6IHt9Cn0=\"));\n    dnsHelpDocumentUrl = \"https://sing-box.sagernet.org/configuration/dns/\";\n    //\n    ui->direct_dns_strategy->addItems(qsValue);\n    ui->remote_dns_strategy->addItems(qsValue);\n    //\n    D_C_LOAD_STRING(custom_route_global)\n    //\n    connect(ui->use_dns_object, &QCheckBox::stateChanged, this, [=](int state) {\n        auto useDNSObject = state == Qt::Checked;\n        ui->simple_dns_box->setDisabled(useDNSObject);\n        ui->dns_object->setDisabled(!useDNSObject);\n    });\n    ui->use_dns_object->stateChanged(Qt::Unchecked); // uncheck to uncheck\n    connect(ui->dns_document, &QPushButton::clicked, this, [=] {\n        MessageBoxInfo(\"DNS\", dnsHelpDocumentUrl);\n    });\n    connect(ui->format_dns_object, &QPushButton::clicked, this, [=] {\n        auto obj = QString2QJsonObject(ui->dns_object->toPlainText());\n        if (obj.isEmpty()) {\n            MessageBoxInfo(\"DNS\", \"invaild json\");\n        } else {\n            ui->dns_object->setPlainText(QJsonObject2QString(obj, false));\n        }\n    });\n    //\n    connect(ui->custom_route_edit, &QPushButton::clicked, this, [=] {\n        C_EDIT_JSON_ALLOW_EMPTY(custom_route)\n    });\n    connect(ui->custom_route_global_edit, &QPushButton::clicked, this, [=] {\n        C_EDIT_JSON_ALLOW_EMPTY(custom_route_global)\n    });\n    //\n    builtInSchemesMenu = new QMenu(this);\n    builtInSchemesMenu->addActions(this->getBuiltInSchemes());\n    ui->preset->setMenu(builtInSchemesMenu);\n\n    QString geoipFn = NekoGui::FindCoreAsset(\"geoip.dat\");\n    QString geositeFn = NekoGui::FindCoreAsset(\"geosite.dat\");\n    //\n    const auto sourceStringsDomain = Qv2ray::components::GeositeReader::ReadGeoSiteFromFile(geositeFn);\n    directDomainTxt = new AutoCompleteTextEdit(\"geosite\", sourceStringsDomain, this);\n    proxyDomainTxt = new AutoCompleteTextEdit(\"geosite\", sourceStringsDomain, this);\n    blockDomainTxt = new AutoCompleteTextEdit(\"geosite\", sourceStringsDomain, this);\n    //\n    const auto sourceStringsIP = Qv2ray::components::GeositeReader::ReadGeoSiteFromFile(geoipFn);\n    directIPTxt = new AutoCompleteTextEdit(\"geoip\", sourceStringsIP, this);\n    proxyIPTxt = new AutoCompleteTextEdit(\"geoip\", sourceStringsIP, this);\n    blockIPTxt = new AutoCompleteTextEdit(\"geoip\", sourceStringsIP, this);\n    //\n    ui->directTxtLayout->addWidget(directDomainTxt, 0, 0);\n    ui->proxyTxtLayout->addWidget(proxyDomainTxt, 0, 0);\n    ui->blockTxtLayout->addWidget(blockDomainTxt, 0, 0);\n    //\n    ui->directIPLayout->addWidget(directIPTxt, 0, 0);\n    ui->proxyIPLayout->addWidget(proxyIPTxt, 0, 0);\n    ui->blockIPLayout->addWidget(blockIPTxt, 0, 0);\n    //\n    REFRESH_ACTIVE_ROUTING(NekoGui::dataStore->active_routing, NekoGui::dataStore->routing.get())\n\n    ADD_ASTERISK(this)\n}\n\nDialogManageRoutes::~DialogManageRoutes() {\n    delete ui;\n}\n\nvoid DialogManageRoutes::accept() {\n    D_C_SAVE_STRING(custom_route_global)\n    bool routeChanged = false;\n    if (NekoGui::dataStore->active_routing != active_routing) routeChanged = true;\n    SaveDisplayRouting(NekoGui::dataStore->routing.get());\n    NekoGui::dataStore->active_routing = active_routing;\n    NekoGui::dataStore->routing->fn = ROUTES_PREFIX + NekoGui::dataStore->active_routing;\n    if (NekoGui::dataStore->routing->Save()) routeChanged = true;\n    //\n    QString info = \"UpdateDataStore\";\n    if (routeChanged) info += \"RouteChanged\";\n    MW_dialog_message(Dialog_DialogManageRoutes, info);\n    QDialog::accept();\n}\n\n// built in settings\n\nQList<QAction *> DialogManageRoutes::getBuiltInSchemes() {\n    QList<QAction *> list;\n    list.append(this->schemeToAction(tr(\"Bypass LAN and China\"), routing_cn_lan));\n    list.append(this->schemeToAction(tr(\"Global\"), routing_global));\n    return list;\n}\n\nQAction *DialogManageRoutes::schemeToAction(const QString &name, const NekoGui::Routing &scheme) {\n    auto *action = new QAction(name, this);\n    connect(action, &QAction::triggered, [this, &scheme] { this->UpdateDisplayRouting((NekoGui::Routing *) &scheme, true); });\n    return action;\n}\n\nvoid DialogManageRoutes::UpdateDisplayRouting(NekoGui::Routing *conf, bool qv) {\n    //\n    directDomainTxt->setPlainText(conf->direct_domain);\n    proxyDomainTxt->setPlainText(conf->proxy_domain);\n    blockDomainTxt->setPlainText(conf->block_domain);\n    //\n    blockIPTxt->setPlainText(conf->block_ip);\n    directIPTxt->setPlainText(conf->direct_ip);\n    proxyIPTxt->setPlainText(conf->proxy_ip);\n    //\n    CACHE.custom_route = conf->custom;\n    ui->def_outbound->setCurrentText(conf->def_outbound);\n    //\n    if (qv) return;\n    //\n    ui->sniffing_mode->setCurrentIndex(conf->sniffing_mode);\n    ui->outbound_domain_strategy->setCurrentText(conf->outbound_domain_strategy);\n    ui->domainStrategyCombo->setCurrentText(conf->domain_strategy);\n    ui->use_dns_object->setChecked(conf->use_dns_object);\n    ui->dns_object->setPlainText(conf->dns_object);\n    ui->dns_routing->setChecked(conf->dns_routing);\n    ui->remote_dns->setCurrentText(conf->remote_dns);\n    ui->remote_dns_strategy->setCurrentText(conf->remote_dns_strategy);\n    ui->direct_dns->setCurrentText(conf->direct_dns);\n    ui->direct_dns_strategy->setCurrentText(conf->direct_dns_strategy);\n    ui->dns_final_out->setCurrentText(conf->dns_final_out);\n}\n\nvoid DialogManageRoutes::SaveDisplayRouting(NekoGui::Routing *conf) {\n    conf->direct_ip = directIPTxt->toPlainText();\n    conf->direct_domain = directDomainTxt->toPlainText();\n    conf->proxy_ip = proxyIPTxt->toPlainText();\n    conf->proxy_domain = proxyDomainTxt->toPlainText();\n    conf->block_ip = blockIPTxt->toPlainText();\n    conf->block_domain = blockDomainTxt->toPlainText();\n    conf->def_outbound = ui->def_outbound->currentText();\n    conf->custom = CACHE.custom_route;\n    //\n    conf->sniffing_mode = ui->sniffing_mode->currentIndex();\n    conf->domain_strategy = ui->domainStrategyCombo->currentText();\n    conf->outbound_domain_strategy = ui->outbound_domain_strategy->currentText();\n    conf->use_dns_object = ui->use_dns_object->isChecked();\n    conf->dns_object = ui->dns_object->toPlainText();\n    conf->dns_routing = ui->dns_routing->isChecked();\n    conf->remote_dns = ui->remote_dns->currentText();\n    conf->remote_dns_strategy = ui->remote_dns_strategy->currentText();\n    conf->direct_dns = ui->direct_dns->currentText();\n    conf->direct_dns_strategy = ui->direct_dns_strategy->currentText();\n    conf->dns_final_out = ui->dns_final_out->currentText();\n}\n\nvoid DialogManageRoutes::on_load_save_clicked() {\n    auto w = new QDialog;\n    auto layout = new QVBoxLayout;\n    w->setLayout(layout);\n    auto lineEdit = new QLineEdit;\n    layout->addWidget(lineEdit);\n    auto list = new QListWidget;\n    layout->addWidget(list);\n    for (const auto &name: NekoGui::Routing::List()) {\n        list->addItem(name);\n    }\n    connect(list, &QListWidget::currentTextChanged, lineEdit, &QLineEdit::setText);\n    auto bottom = new QHBoxLayout;\n    layout->addLayout(bottom);\n    auto load = new QPushButton;\n    load->setText(tr(\"Load\"));\n    bottom->addWidget(load);\n    auto save = new QPushButton;\n    save->setText(tr(\"Save\"));\n    bottom->addWidget(save);\n    auto remove = new QPushButton;\n    remove->setText(tr(\"Remove\"));\n    bottom->addWidget(remove);\n    auto cancel = new QPushButton;\n    cancel->setText(tr(\"Cancel\"));\n    bottom->addWidget(cancel);\n    connect(load, &QPushButton::clicked, w, [=] {\n        auto fn = lineEdit->text();\n        if (!fn.isEmpty()) {\n            auto r = std::make_unique<NekoGui::Routing>();\n            r->load_control_must = true;\n            r->fn = ROUTES_PREFIX + fn;\n            if (r->Load()) {\n                if (QMessageBox::question(nullptr, software_name, tr(\"Load routing: %1\").arg(fn) + \"\\n\" + r->DisplayRouting()) == QMessageBox::Yes) {\n                    REFRESH_ACTIVE_ROUTING(fn, r.get()) // temp save to the window\n                    w->accept();\n                }\n            }\n        }\n    });\n    connect(save, &QPushButton::clicked, w, [=] {\n        auto fn = lineEdit->text();\n        if (!fn.isEmpty()) {\n            auto r = std::make_unique<NekoGui::Routing>();\n            SaveDisplayRouting(r.get());\n            r->fn = ROUTES_PREFIX + fn;\n            if (QMessageBox::question(nullptr, software_name, tr(\"Save routing: %1\").arg(fn) + \"\\n\" + r->DisplayRouting()) == QMessageBox::Yes) {\n                r->Save();\n                REFRESH_ACTIVE_ROUTING(fn, r.get())\n                w->accept();\n            }\n        }\n    });\n    connect(remove, &QPushButton::clicked, w, [=] {\n        auto fn = lineEdit->text();\n        if (!fn.isEmpty() && NekoGui::Routing::List().length() > 1) {\n            if (QMessageBox::question(nullptr, software_name, tr(\"Remove routing: %1\").arg(fn)) == QMessageBox::Yes) {\n                QFile f(ROUTES_PREFIX + fn);\n                f.remove();\n                if (NekoGui::dataStore->active_routing == fn) {\n                    NekoGui::Routing::SetToActive(NekoGui::Routing::List().first());\n                    REFRESH_ACTIVE_ROUTING(NekoGui::dataStore->active_routing, NekoGui::dataStore->routing.get())\n                }\n                w->accept();\n            }\n        }\n    });\n    connect(cancel, &QPushButton::clicked, w, &QDialog::accept);\n    connect(list, &QListWidget::itemDoubleClicked, this, [=](QListWidgetItem *item) {\n        lineEdit->setText(item->text());\n        emit load->clicked();\n    });\n    w->exec();\n    w->deleteLater();\n}\n"
  },
  {
    "path": "ui/dialog_manage_routes.h",
    "content": "#pragma once\n\n#include <QDialog>\n#include <QMenu>\n\n#include \"3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.hpp\"\n#include \"main/NekoGui.hpp\"\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class DialogManageRoutes;\n}\nQT_END_NAMESPACE\n\nclass DialogManageRoutes : public QDialog {\n    Q_OBJECT\n\npublic:\n    explicit DialogManageRoutes(QWidget *parent = nullptr);\n\n    ~DialogManageRoutes() override;\n\nprivate:\n    Ui::DialogManageRoutes *ui;\n\n    struct {\n        QString custom_route;\n        QString custom_route_global;\n    } CACHE;\n\n    QMenu *builtInSchemesMenu;\n    Qv2ray::ui::widgets::AutoCompleteTextEdit *directDomainTxt;\n    Qv2ray::ui::widgets::AutoCompleteTextEdit *proxyDomainTxt;\n    Qv2ray::ui::widgets::AutoCompleteTextEdit *blockDomainTxt;\n    //\n    Qv2ray::ui::widgets::AutoCompleteTextEdit *directIPTxt;\n    Qv2ray::ui::widgets::AutoCompleteTextEdit *blockIPTxt;\n    Qv2ray::ui::widgets::AutoCompleteTextEdit *proxyIPTxt;\n    //\n    NekoGui::Routing routing_cn_lan = NekoGui::Routing(1);\n    NekoGui::Routing routing_global = NekoGui::Routing(0);\n    //\n    QString title_base;\n    QString active_routing;\n\npublic slots:\n\n    void accept() override;\n\n    QList<QAction *> getBuiltInSchemes();\n\n    QAction *schemeToAction(const QString &name, const NekoGui::Routing &scheme);\n\n    void UpdateDisplayRouting(NekoGui::Routing *conf, bool qv);\n\n    void SaveDisplayRouting(NekoGui::Routing *conf);\n\n    void on_load_save_clicked();\n};\n"
  },
  {
    "path": "ui/dialog_manage_routes.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>DialogManageRoutes</class>\n <widget class=\"QDialog\" name=\"DialogManageRoutes\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>800</width>\n    <height>600</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Routes</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <widget class=\"QTabWidget\" name=\"tabWidget\">\n     <property name=\"currentIndex\">\n      <number>0</number>\n     </property>\n     <widget class=\"QWidget\" name=\"tab\">\n      <attribute name=\"title\">\n       <string>Common</string>\n      </attribute>\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\">\n       <item>\n        <widget class=\"QWidget\" name=\"widget\" native=\"true\">\n         <layout class=\"QGridLayout\" name=\"gridLayout_2\">\n          <item row=\"4\" column=\"1\">\n           <spacer name=\"verticalSpacer\">\n            <property name=\"orientation\">\n             <enum>Qt::Vertical</enum>\n            </property>\n            <property name=\"sizeHint\" stdset=\"0\">\n             <size>\n              <width>20</width>\n              <height>40</height>\n             </size>\n            </property>\n           </spacer>\n          </item>\n          <item row=\"5\" column=\"1\">\n           <widget class=\"QGroupBox\" name=\"groupBox\">\n            <property name=\"title\">\n             <string>Route sets</string>\n            </property>\n            <layout class=\"QVBoxLayout\" name=\"verticalLayout_7\">\n             <item>\n              <widget class=\"QPushButton\" name=\"load_save\">\n               <property name=\"text\">\n                <string>Mange route set</string>\n               </property>\n              </widget>\n             </item>\n             <item>\n              <widget class=\"QPushButton\" name=\"custom_route_global_edit\">\n               <property name=\"text\">\n                <string>Custom Route (global)</string>\n               </property>\n              </widget>\n             </item>\n             <item>\n              <widget class=\"QLabel\" name=\"label_5\">\n               <property name=\"text\">\n                <string>Note: Other settings are independent for each route set.</string>\n               </property>\n              </widget>\n             </item>\n            </layout>\n           </widget>\n          </item>\n          <item row=\"2\" column=\"0\">\n           <widget class=\"QLabel\" name=\"label_6\">\n            <property name=\"toolTip\">\n             <string notr=\"true\">For V2Ray, it sets routing.domainStrategy\nFor sing-box, it sets inbound.domain_strategy</string>\n            </property>\n            <property name=\"text\">\n             <string>Domain Strategy</string>\n            </property>\n           </widget>\n          </item>\n          <item row=\"3\" column=\"1\">\n           <widget class=\"QComboBox\" name=\"outbound_domain_strategy\">\n            <property name=\"editable\">\n             <bool>false</bool>\n            </property>\n           </widget>\n          </item>\n          <item row=\"1\" column=\"1\">\n           <widget class=\"QComboBox\" name=\"sniffing_mode\">\n            <item>\n             <property name=\"text\">\n              <string>Disable</string>\n             </property>\n            </item>\n            <item>\n             <property name=\"text\">\n              <string>Sniff result for routing</string>\n             </property>\n            </item>\n            <item>\n             <property name=\"text\">\n              <string>Sniff result for destination</string>\n             </property>\n            </item>\n           </widget>\n          </item>\n          <item row=\"1\" column=\"0\">\n           <widget class=\"QLabel\" name=\"label\">\n            <property name=\"text\">\n             <string>Sniffing Mode</string>\n            </property>\n           </widget>\n          </item>\n          <item row=\"3\" column=\"0\">\n           <widget class=\"QLabel\" name=\"label_3\">\n            <property name=\"text\">\n             <string>Server Address Strategy</string>\n            </property>\n           </widget>\n          </item>\n          <item row=\"2\" column=\"1\">\n           <widget class=\"QComboBox\" name=\"domainStrategyCombo\">\n            <property name=\"editable\">\n             <bool>false</bool>\n            </property>\n           </widget>\n          </item>\n          <item row=\"1\" column=\"2\">\n           <spacer name=\"horizontalSpacer_3\">\n            <property name=\"orientation\">\n             <enum>Qt::Horizontal</enum>\n            </property>\n            <property name=\"sizeHint\" stdset=\"0\">\n             <size>\n              <width>40</width>\n              <height>20</height>\n             </size>\n            </property>\n           </spacer>\n          </item>\n         </layout>\n        </widget>\n       </item>\n      </layout>\n     </widget>\n     <widget class=\"QWidget\" name=\"tab_3\">\n      <attribute name=\"title\">\n       <string>DNS</string>\n      </attribute>\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout_4\">\n       <item>\n        <widget class=\"QGroupBox\" name=\"simple_dns_box\">\n         <property name=\"title\">\n          <string>Simple DNS Settings</string>\n         </property>\n         <layout class=\"QGridLayout\" name=\"gridLayout\">\n          <item row=\"1\" column=\"0\">\n           <widget class=\"QLabel\" name=\"label_8\">\n            <property name=\"toolTip\">\n             <string>This is especially important and it is recommended to use the default value of &quot;localhost&quot;.\nIf the default value does not work, try changing it to &quot;223.5.5.5&quot;.\nFor more information, see the document &quot;Configuration/DNS&quot;.</string>\n            </property>\n            <property name=\"text\">\n             <string>Direct DNS</string>\n            </property>\n           </widget>\n          </item>\n          <item row=\"0\" column=\"2\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_7\">\n            <item>\n             <widget class=\"QComboBox\" name=\"remote_dns\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"editable\">\n               <bool>true</bool>\n              </property>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">https://8.8.8.8/dns-query</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">https://1.0.0.1/dns-query</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">https://1.1.1.1/dns-query</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">https://dns.google/dns-query</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">https://one.one.one.one/dns-query</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">https://[2001:4860:4860::8888]/dns-query</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">https://[2606:4700:4700::1111]/dns-query</string>\n               </property>\n              </item>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QLabel\" name=\"label_10\">\n              <property name=\"text\">\n               <string>Query Strategy</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QComboBox\" name=\"remote_dns_strategy\"/>\n            </item>\n           </layout>\n          </item>\n          <item row=\"0\" column=\"0\">\n           <widget class=\"QLabel\" name=\"label_9\">\n            <property name=\"text\">\n             <string>Remote DNS</string>\n            </property>\n            <property name=\"buddy\">\n             <cstring>widget</cstring>\n            </property>\n           </widget>\n          </item>\n          <item row=\"2\" column=\"2\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\n            <item>\n             <widget class=\"QCheckBox\" name=\"dns_routing\">\n              <property name=\"text\">\n               <string>Enable DNS Routing</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <spacer name=\"horizontalSpacer_2\">\n              <property name=\"orientation\">\n               <enum>Qt::Horizontal</enum>\n              </property>\n              <property name=\"sizeHint\" stdset=\"0\">\n               <size>\n                <width>40</width>\n                <height>20</height>\n               </size>\n              </property>\n             </spacer>\n            </item>\n            <item>\n             <widget class=\"QLabel\" name=\"dns_final_out_l\">\n              <property name=\"text\">\n               <string>Final DNS Out</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QComboBox\" name=\"dns_final_out\">\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">proxy</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">bypass</string>\n               </property>\n              </item>\n             </widget>\n            </item>\n           </layout>\n          </item>\n          <item row=\"1\" column=\"2\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_8\">\n            <item>\n             <widget class=\"QComboBox\" name=\"direct_dns\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"editable\">\n               <bool>true</bool>\n              </property>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">local</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">tls://120.53.53.53</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">https://223.5.5.5/dns-query</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">https://1.12.12.12/dns-query</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">https://dns.alidns.com/dns-query</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">https://doh.pub/dns-query</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">223.5.5.5</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">119.29.29.29</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">2400:3200::1</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">2402:4e00::</string>\n               </property>\n              </item>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QLabel\" name=\"label_11\">\n              <property name=\"text\">\n               <string>Query Strategy</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QComboBox\" name=\"direct_dns_strategy\"/>\n            </item>\n           </layout>\n          </item>\n         </layout>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"QGroupBox\" name=\"groupBox_2\">\n         <property name=\"title\">\n          <string>DNS Object Settings</string>\n         </property>\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_5\">\n          <item>\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_4\">\n            <item>\n             <widget class=\"QCheckBox\" name=\"use_dns_object\">\n              <property name=\"text\">\n               <string>Use DNS Object</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"format_dns_object\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Fixed\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>Format</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"dns_document\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Fixed\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>Document</string>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </item>\n          <item>\n           <widget class=\"QPlainTextEdit\" name=\"dns_object\"/>\n          </item>\n         </layout>\n        </widget>\n       </item>\n      </layout>\n     </widget>\n     <widget class=\"QWidget\" name=\"tab_2\">\n      <attribute name=\"title\">\n       <string>Simple Route</string>\n      </attribute>\n      <layout class=\"QVBoxLayout\" name=\"verticalLayout_6\">\n       <item>\n        <widget class=\"QGroupBox\" name=\"gb2\">\n         <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n          <item>\n           <layout class=\"QGridLayout\" name=\"gridLayout_3\" rowstretch=\"0,1,1\" columnstretch=\"0,1,1,1\">\n            <item row=\"0\" column=\"3\">\n             <widget class=\"QLabel\" name=\"label_82\">\n              <property name=\"text\">\n               <string>Block</string>\n              </property>\n              <property name=\"alignment\">\n               <set>Qt::AlignCenter</set>\n              </property>\n             </widget>\n            </item>\n            <item row=\"2\" column=\"3\">\n             <layout class=\"QGridLayout\" name=\"blockTxtLayout\"/>\n            </item>\n            <item row=\"0\" column=\"1\">\n             <widget class=\"QLabel\" name=\"label_80\">\n              <property name=\"text\">\n               <string>Direct</string>\n              </property>\n              <property name=\"alignment\">\n               <set>Qt::AlignCenter</set>\n              </property>\n             </widget>\n            </item>\n            <item row=\"2\" column=\"0\">\n             <widget class=\"QLabel\" name=\"label_7\">\n              <property name=\"text\">\n               <string>Domain</string>\n              </property>\n              <property name=\"alignment\">\n               <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>\n              </property>\n             </widget>\n            </item>\n            <item row=\"2\" column=\"1\">\n             <layout class=\"QGridLayout\" name=\"directTxtLayout\"/>\n            </item>\n            <item row=\"1\" column=\"2\">\n             <layout class=\"QGridLayout\" name=\"proxyIPLayout\"/>\n            </item>\n            <item row=\"0\" column=\"2\">\n             <widget class=\"QLabel\" name=\"label_81\">\n              <property name=\"text\">\n               <string>Proxy</string>\n              </property>\n              <property name=\"alignment\">\n               <set>Qt::AlignCenter</set>\n              </property>\n             </widget>\n            </item>\n            <item row=\"2\" column=\"2\">\n             <layout class=\"QGridLayout\" name=\"proxyTxtLayout\"/>\n            </item>\n            <item row=\"1\" column=\"3\">\n             <layout class=\"QGridLayout\" name=\"blockIPLayout\"/>\n            </item>\n            <item row=\"1\" column=\"0\">\n             <widget class=\"QLabel\" name=\"label_2\">\n              <property name=\"text\">\n               <string>IP</string>\n              </property>\n              <property name=\"alignment\">\n               <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>\n              </property>\n             </widget>\n            </item>\n            <item row=\"1\" column=\"1\">\n             <layout class=\"QGridLayout\" name=\"directIPLayout\"/>\n            </item>\n           </layout>\n          </item>\n          <item>\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_5\">\n            <item>\n             <widget class=\"QWidget\" name=\"horizontalWidget_2\" native=\"true\">\n              <layout class=\"QHBoxLayout\" name=\"horizontalLayout_6\">\n               <property name=\"leftMargin\">\n                <number>0</number>\n               </property>\n               <property name=\"topMargin\">\n                <number>0</number>\n               </property>\n               <property name=\"rightMargin\">\n                <number>0</number>\n               </property>\n               <property name=\"bottomMargin\">\n                <number>0</number>\n               </property>\n               <item>\n                <widget class=\"QToolButton\" name=\"preset\">\n                 <property name=\"sizePolicy\">\n                  <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Fixed\">\n                   <horstretch>0</horstretch>\n                   <verstretch>0</verstretch>\n                  </sizepolicy>\n                 </property>\n                 <property name=\"text\">\n                  <string>Preset</string>\n                 </property>\n                 <property name=\"popupMode\">\n                  <enum>QToolButton::InstantPopup</enum>\n                 </property>\n                 <property name=\"toolButtonStyle\">\n                  <enum>Qt::ToolButtonTextBesideIcon</enum>\n                 </property>\n                </widget>\n               </item>\n               <item>\n                <widget class=\"QPushButton\" name=\"custom_route_edit\">\n                 <property name=\"text\">\n                  <string>Custom Route</string>\n                 </property>\n                </widget>\n               </item>\n              </layout>\n             </widget>\n            </item>\n            <item>\n             <spacer name=\"horizontalSpacer\">\n              <property name=\"orientation\">\n               <enum>Qt::Horizontal</enum>\n              </property>\n              <property name=\"sizeHint\" stdset=\"0\">\n               <size>\n                <width>40</width>\n                <height>20</height>\n               </size>\n              </property>\n             </spacer>\n            </item>\n            <item>\n             <widget class=\"QWidget\" name=\"horizontalWidget\" native=\"true\">\n              <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n               <property name=\"leftMargin\">\n                <number>0</number>\n               </property>\n               <property name=\"topMargin\">\n                <number>0</number>\n               </property>\n               <property name=\"rightMargin\">\n                <number>0</number>\n               </property>\n               <property name=\"bottomMargin\">\n                <number>0</number>\n               </property>\n               <item>\n                <widget class=\"QLabel\" name=\"label_4\">\n                 <property name=\"text\">\n                  <string>Default Outbound</string>\n                 </property>\n                </widget>\n               </item>\n               <item>\n                <widget class=\"QComboBox\" name=\"def_outbound\">\n                 <item>\n                  <property name=\"text\">\n                   <string notr=\"true\">proxy</string>\n                  </property>\n                 </item>\n                 <item>\n                  <property name=\"text\">\n                   <string notr=\"true\">bypass</string>\n                  </property>\n                 </item>\n                 <item>\n                  <property name=\"text\">\n                   <string notr=\"true\">block</string>\n                  </property>\n                 </item>\n                </widget>\n               </item>\n              </layout>\n             </widget>\n            </item>\n           </layout>\n          </item>\n         </layout>\n        </widget>\n       </item>\n      </layout>\n     </widget>\n    </widget>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <item>\n      <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"standardButtons\">\n        <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <tabstops>\n  <tabstop>tabWidget</tabstop>\n  <tabstop>sniffing_mode</tabstop>\n  <tabstop>domainStrategyCombo</tabstop>\n  <tabstop>outbound_domain_strategy</tabstop>\n  <tabstop>load_save</tabstop>\n  <tabstop>custom_route_global_edit</tabstop>\n  <tabstop>remote_dns</tabstop>\n  <tabstop>remote_dns_strategy</tabstop>\n  <tabstop>direct_dns</tabstop>\n  <tabstop>direct_dns_strategy</tabstop>\n  <tabstop>dns_routing</tabstop>\n  <tabstop>dns_final_out</tabstop>\n  <tabstop>use_dns_object</tabstop>\n  <tabstop>format_dns_object</tabstop>\n  <tabstop>dns_document</tabstop>\n  <tabstop>dns_object</tabstop>\n  <tabstop>preset</tabstop>\n  <tabstop>custom_route_edit</tabstop>\n  <tabstop>def_outbound</tabstop>\n </tabstops>\n <resources/>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>rejected()</signal>\n   <receiver>DialogManageRoutes</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>399</x>\n     <y>574</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>accepted()</signal>\n   <receiver>DialogManageRoutes</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>399</x>\n     <y>574</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "ui/dialog_vpn_settings.cpp",
    "content": "#include \"dialog_vpn_settings.h\"\n#include \"ui_dialog_vpn_settings.h\"\n\n#include \"main/GuiUtils.hpp\"\n#include \"main/NekoGui.hpp\"\n#include \"ui/mainwindow_interface.h\"\n\n#include <QMessageBox>\n\nDialogVPNSettings::DialogVPNSettings(QWidget *parent) : QDialog(parent), ui(new Ui::DialogVPNSettings) {\n    ui->setupUi(this);\n    ADD_ASTERISK(this);\n\n    ui->fake_dns->setChecked(NekoGui::dataStore->fake_dns);\n    ui->vpn_implementation->setCurrentIndex(NekoGui::dataStore->vpn_implementation);\n    ui->vpn_mtu->setCurrentText(Int2String(NekoGui::dataStore->vpn_mtu));\n    ui->vpn_ipv6->setChecked(NekoGui::dataStore->vpn_ipv6);\n    ui->hide_console->setChecked(NekoGui::dataStore->vpn_hide_console);\n#ifndef Q_OS_WIN\n    ui->hide_console->setVisible(false);\n#endif\n    ui->strict_route->setChecked(NekoGui::dataStore->vpn_strict_route);\n    ui->single_core->setChecked(NekoGui::dataStore->vpn_internal_tun);\n    //\n    D_LOAD_STRING_PLAIN(vpn_rule_cidr)\n    D_LOAD_STRING_PLAIN(vpn_rule_process)\n    //\n    connect(ui->whitelist_mode, &QCheckBox::stateChanged, this, [=](int state) {\n        if (state == Qt::Checked) {\n            ui->gb_cidr->setTitle(tr(\"Proxy CIDR\"));\n            ui->gb_process_name->setTitle(tr(\"Proxy Process Name\"));\n        } else {\n            ui->gb_cidr->setTitle(tr(\"Bypass CIDR\"));\n            ui->gb_process_name->setTitle(tr(\"Bypass Process Name\"));\n        }\n    });\n    ui->whitelist_mode->setChecked(NekoGui::dataStore->vpn_rule_white);\n}\n\nDialogVPNSettings::~DialogVPNSettings() {\n    delete ui;\n}\n\nvoid DialogVPNSettings::accept() {\n    //\n    auto mtu = ui->vpn_mtu->currentText().toInt();\n    if (mtu > 10000 || mtu < 1000) mtu = 9000;\n    NekoGui::dataStore->vpn_implementation = ui->vpn_implementation->currentIndex();\n    NekoGui::dataStore->fake_dns = ui->fake_dns->isChecked();\n    NekoGui::dataStore->vpn_mtu = mtu;\n    NekoGui::dataStore->vpn_ipv6 = ui->vpn_ipv6->isChecked();\n    NekoGui::dataStore->vpn_hide_console = ui->hide_console->isChecked();\n    NekoGui::dataStore->vpn_strict_route = ui->strict_route->isChecked();\n    NekoGui::dataStore->vpn_rule_white = ui->whitelist_mode->isChecked();\n    bool isInternalChanged = NekoGui::dataStore->vpn_internal_tun != ui->single_core->isChecked();\n    NekoGui::dataStore->vpn_internal_tun = ui->single_core->isChecked();\n    //\n    D_SAVE_STRING_PLAIN(vpn_rule_cidr)\n    D_SAVE_STRING_PLAIN(vpn_rule_process)\n    //\n    QStringList msg{\"UpdateDataStore\"};\n    if (isInternalChanged) {\n        msg << \"NeedRestart\";\n    } else {\n        msg << \"VPNChanged\";\n    }\n    MW_dialog_message(\"\", msg.join(\",\"));\n    QDialog::accept();\n}\n\nvoid DialogVPNSettings::on_troubleshooting_clicked() {\n    auto r = QMessageBox::information(this, tr(\"Troubleshooting\"),\n                                      tr(\"If you have trouble starting VPN, you can force reset nekobox_core process here.\\n\\n\"\n                                         \"If still not working, see documentation for more information.\\n\"\n                                         \"https://matsuridayo.github.io/n-configuration/#vpn-tun\"),\n                                      tr(\"Reset\"), tr(\"Cancel\"), \"\",\n                                      1, 1);\n    if (r == 0) {\n        GetMainWindow()->StopVPNProcess(true);\n    }\n}\n"
  },
  {
    "path": "ui/dialog_vpn_settings.h",
    "content": "#ifndef NEKORAY_DIALOG_VPN_SETTINGS_H\n#define NEKORAY_DIALOG_VPN_SETTINGS_H\n\n#include <QDialog>\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class DialogVPNSettings;\n}\nQT_END_NAMESPACE\n\nclass DialogVPNSettings : public QDialog {\n    Q_OBJECT\n\npublic:\n    explicit DialogVPNSettings(QWidget *parent = nullptr);\n\n    ~DialogVPNSettings() override;\n\nprivate:\n    Ui::DialogVPNSettings *ui;\n\npublic slots:\n\n    void accept() override;\n\n    void on_troubleshooting_clicked();\n};\n\n#endif // NEKORAY_DIALOG_VPN_SETTINGS_H\n"
  },
  {
    "path": "ui/dialog_vpn_settings.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>DialogVPNSettings</class>\n <widget class=\"QDialog\" name=\"DialogVPNSettings\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>600</width>\n    <height>600</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Tun Settings</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBox\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <layout class=\"QHBoxLayout\" name=\"horizontalLayout_5\">\n      <item>\n       <widget class=\"QLabel\" name=\"label_4\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"text\">\n         <string notr=\"true\">Stack</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QComboBox\" name=\"vpn_implementation\">\n        <item>\n         <property name=\"text\">\n          <string notr=\"true\">Mixed</string>\n         </property>\n        </item>\n        <item>\n         <property name=\"text\">\n          <string notr=\"true\">gVisor</string>\n         </property>\n        </item>\n        <item>\n         <property name=\"text\">\n          <string notr=\"true\">System</string>\n         </property>\n        </item>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QLabel\" name=\"label_10\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"text\">\n         <string notr=\"true\">MTU</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QComboBox\" name=\"vpn_mtu\">\n        <property name=\"editable\">\n         <bool>true</bool>\n        </property>\n        <item>\n         <property name=\"text\">\n          <string notr=\"true\">9000</string>\n         </property>\n        </item>\n        <item>\n         <property name=\"text\">\n          <string notr=\"true\">1500</string>\n         </property>\n        </item>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QGroupBox\" name=\"horizontalGroupBox_2\">\n     <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n      <item>\n       <widget class=\"QCheckBox\" name=\"vpn_ipv6\">\n        <property name=\"text\">\n         <string>Tun Enable IPv6</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QCheckBox\" name=\"strict_route\">\n        <property name=\"text\">\n         <string notr=\"true\">Strict Route</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QCheckBox\" name=\"fake_dns\">\n        <property name=\"text\">\n         <string notr=\"true\">FakeDNS</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"Line\" name=\"line\">\n        <property name=\"orientation\">\n         <enum>Qt::Vertical</enum>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QCheckBox\" name=\"single_core\">\n        <property name=\"toolTip\">\n         <string>Add a tun inbound to the profile startup, instead of using two processes.\nThis needs to be run NekoBox with administrator privileges.</string>\n        </property>\n        <property name=\"text\">\n         <string>Internal Tun</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QCheckBox\" name=\"hide_console\">\n        <property name=\"text\">\n         <string>Hide Console</string>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n     <item>\n      <widget class=\"QGroupBox\" name=\"gb_cidr\">\n       <property name=\"title\">\n        <string>Bypass CIDR</string>\n       </property>\n       <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n        <item>\n         <widget class=\"QPlainTextEdit\" name=\"vpn_rule_cidr\"/>\n        </item>\n       </layout>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QGroupBox\" name=\"gb_process_name\">\n       <property name=\"title\">\n        <string>Bypass Process Name</string>\n       </property>\n       <layout class=\"QVBoxLayout\" name=\"verticalLayout_4\">\n        <item>\n         <widget class=\"QPlainTextEdit\" name=\"vpn_rule_process\"/>\n        </item>\n       </layout>\n      </widget>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\n     <item>\n      <widget class=\"QCheckBox\" name=\"whitelist_mode\">\n       <property name=\"toolTip\">\n        <string>Whether blacklisted or whitelisted, your traffic will be handled by nekobox_core (sing-tun). This is NOT equal to &quot;process mode&quot; of some software.</string>\n       </property>\n       <property name=\"text\">\n        <string>Whitelist mode</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <spacer name=\"horizontalSpacer\">\n       <property name=\"orientation\">\n        <enum>Qt::Horizontal</enum>\n       </property>\n       <property name=\"sizeHint\" stdset=\"0\">\n        <size>\n         <width>40</width>\n         <height>20</height>\n        </size>\n       </property>\n      </spacer>\n     </item>\n     <item>\n      <widget class=\"QPushButton\" name=\"troubleshooting\">\n       <property name=\"text\">\n        <string>Troubleshooting</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n       <property name=\"standardButtons\">\n        <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <tabstops>\n  <tabstop>vpn_implementation</tabstop>\n  <tabstop>vpn_mtu</tabstop>\n  <tabstop>vpn_ipv6</tabstop>\n  <tabstop>strict_route</tabstop>\n  <tabstop>fake_dns</tabstop>\n  <tabstop>single_core</tabstop>\n  <tabstop>hide_console</tabstop>\n  <tabstop>vpn_rule_cidr</tabstop>\n  <tabstop>vpn_rule_process</tabstop>\n  <tabstop>whitelist_mode</tabstop>\n  <tabstop>troubleshooting</tabstop>\n </tabstops>\n <resources/>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>accepted()</signal>\n   <receiver>DialogVPNSettings</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>399</x>\n     <y>575</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>rejected()</signal>\n   <receiver>DialogVPNSettings</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>399</x>\n     <y>575</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "ui/edit/dialog_edit_group.cpp",
    "content": "#include \"dialog_edit_group.h\"\n#include \"ui_dialog_edit_group.h\"\n\n#include \"db/Database.hpp\"\n#include \"ui/mainwindow_interface.h\"\n\n#include <QClipboard>\n\n#define ADJUST_SIZE runOnUiThread([=] { adjustSize(); adjustPosition(mainwindow); }, this);\n\nDialogEditGroup::DialogEditGroup(const std::shared_ptr<NekoGui::Group> &ent, QWidget *parent) : QDialog(parent), ui(new Ui::DialogEditGroup) {\n    ui->setupUi(this);\n    this->ent = ent;\n\n    connect(ui->type, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) {\n        ui->cat_sub->setHidden(index == 0);\n        ADJUST_SIZE\n    });\n\n    ui->name->setText(ent->name);\n    ui->archive->setChecked(ent->archive);\n    ui->skip_auto_update->setChecked(ent->skip_auto_update);\n    ui->url->setText(ent->url);\n    ui->type->setCurrentIndex(ent->url.isEmpty() ? 0 : 1);\n    ui->type->currentIndexChanged(ui->type->currentIndex());\n    ui->manually_column_width->setChecked(ent->manually_column_width);\n    ui->cat_share->setVisible(false);\n\n    if (ent->id >= 0) { // already a group\n        ui->type->setDisabled(true);\n        if (!ent->Profiles().isEmpty()) {\n            ui->cat_share->setVisible(true);\n        }\n    } else { // new group\n        ui->front_proxy->hide();\n        ui->front_proxy_l->hide();\n        ui->front_proxy_clear->hide();\n    }\n\n    CACHE.front_proxy = ent->front_proxy_id;\n    refresh_front_proxy();\n    connect(ui->front_proxy_clear, &QPushButton::clicked, this, [=] {\n        CACHE.front_proxy = -1;\n        refresh_front_proxy();\n    });\n\n    connect(ui->copy_links, &QPushButton::clicked, this, [=] {\n        QStringList links;\n        for (const auto &[_, profile]: NekoGui::profileManager->profiles) {\n            if (profile->gid != ent->id) continue;\n            links += profile->bean->ToShareLink();\n        }\n        QApplication::clipboard()->setText(links.join(\"\\n\"));\n        MessageBoxInfo(software_name, tr(\"Copied\"));\n    });\n    connect(ui->copy_links_nkr, &QPushButton::clicked, this, [=] {\n        QStringList links;\n        for (const auto &[_, profile]: NekoGui::profileManager->profiles) {\n            if (profile->gid != ent->id) continue;\n            links += profile->bean->ToNekorayShareLink(profile->type);\n        }\n        QApplication::clipboard()->setText(links.join(\"\\n\"));\n        MessageBoxInfo(software_name, tr(\"Copied\"));\n    });\n\n    ADJUST_SIZE\n}\n\nDialogEditGroup::~DialogEditGroup() {\n    delete ui;\n}\n\nvoid DialogEditGroup::accept() {\n    if (ent->id >= 0) { // already a group\n        if (!ent->url.isEmpty() && ui->url->text().isEmpty()) {\n            MessageBoxWarning(tr(\"Warning\"), tr(\"Please input URL\"));\n            return;\n        }\n    }\n    ent->name = ui->name->text();\n    ent->url = ui->url->text();\n    ent->archive = ui->archive->isChecked();\n    ent->skip_auto_update = ui->skip_auto_update->isChecked();\n    ent->manually_column_width = ui->manually_column_width->isChecked();\n    ent->front_proxy_id = CACHE.front_proxy;\n    QDialog::accept();\n}\n\nvoid DialogEditGroup::refresh_front_proxy() {\n    auto fEnt = NekoGui::profileManager->GetProfile(CACHE.front_proxy);\n    ui->front_proxy->setText(fEnt == nullptr ? tr(\"None\") : fEnt->bean->DisplayTypeAndName());\n}\n\nvoid DialogEditGroup::on_front_proxy_clicked() {\n    auto parent = dynamic_cast<QWidget *>(this->parent());\n    parent->hide();\n    this->hide();\n    GetMainWindow()->start_select_mode(this, [=](int id) {\n        CACHE.front_proxy = id;\n        refresh_front_proxy();\n        parent->show();\n        show();\n    });\n}\n"
  },
  {
    "path": "ui/edit/dialog_edit_group.h",
    "content": "#pragma once\n\n#include <QDialog>\n#include \"db/Group.hpp\"\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class DialogEditGroup;\n}\nQT_END_NAMESPACE\n\nclass DialogEditGroup : public QDialog {\n    Q_OBJECT\n\npublic:\n    explicit DialogEditGroup(const std::shared_ptr<NekoGui::Group> &ent, QWidget *parent = nullptr);\n\n    ~DialogEditGroup() override;\n\nprivate:\n    Ui::DialogEditGroup *ui;\n\n    std::shared_ptr<NekoGui::Group> ent;\n\n    struct {\n        int front_proxy;\n    } CACHE;\n\n    void refresh_front_proxy();\n\nprivate slots:\n\n    void accept() override;\n\n    void on_front_proxy_clicked();\n};\n"
  },
  {
    "path": "ui/edit/dialog_edit_group.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>DialogEditGroup</class>\n <widget class=\"QDialog\" name=\"DialogEditGroup\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>400</width>\n    <height>468</height>\n   </rect>\n  </property>\n  <property name=\"minimumSize\">\n   <size>\n    <width>400</width>\n    <height>300</height>\n   </size>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Edit Group</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <widget class=\"QGroupBox\" name=\"cat_common\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <property name=\"title\">\n      <string>Common</string>\n     </property>\n     <layout class=\"QGridLayout\" name=\"main\">\n      <item row=\"0\" column=\"1\">\n       <widget class=\"QLineEdit\" name=\"name\"/>\n      </item>\n      <item row=\"1\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label_2\">\n        <property name=\"text\">\n         <string>Type</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"1\" column=\"1\">\n       <widget class=\"QComboBox\" name=\"type\">\n        <item>\n         <property name=\"text\">\n          <string>Basic</string>\n         </property>\n        </item>\n        <item>\n         <property name=\"text\">\n          <string>Subscription</string>\n         </property>\n        </item>\n       </widget>\n      </item>\n      <item row=\"2\" column=\"0\">\n       <widget class=\"QLabel\" name=\"front_proxy_l\">\n        <property name=\"text\">\n         <string>Front Proxy</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"3\" column=\"1\">\n       <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n        <item>\n         <widget class=\"QCheckBox\" name=\"manually_column_width\">\n          <property name=\"text\">\n           <string>Manually column width</string>\n          </property>\n         </widget>\n        </item>\n        <item>\n         <widget class=\"QCheckBox\" name=\"archive\">\n          <property name=\"text\">\n           <string>Archive</string>\n          </property>\n         </widget>\n        </item>\n       </layout>\n      </item>\n      <item row=\"0\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label\">\n        <property name=\"text\">\n         <string>Name</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"2\" column=\"1\">\n       <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n        <item>\n         <widget class=\"QPushButton\" name=\"front_proxy\">\n          <property name=\"text\">\n           <string notr=\"true\"/>\n          </property>\n         </widget>\n        </item>\n        <item>\n         <widget class=\"QPushButton\" name=\"front_proxy_clear\">\n          <property name=\"sizePolicy\">\n           <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Fixed\">\n            <horstretch>0</horstretch>\n            <verstretch>0</verstretch>\n           </sizepolicy>\n          </property>\n          <property name=\"text\">\n           <string>Clear</string>\n          </property>\n         </widget>\n        </item>\n       </layout>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QGroupBox\" name=\"cat_sub\">\n     <property name=\"title\">\n      <string>Subscription</string>\n     </property>\n     <layout class=\"QGridLayout\" name=\"_2\">\n      <item row=\"0\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label_4\">\n        <property name=\"text\">\n         <string>URL</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"0\" column=\"1\">\n       <widget class=\"MyLineEdit\" name=\"url\"/>\n      </item>\n      <item row=\"1\" column=\"1\">\n       <widget class=\"QCheckBox\" name=\"skip_auto_update\">\n        <property name=\"text\">\n         <string>Skip automatic update</string>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QGroupBox\" name=\"cat_share\">\n     <property name=\"title\">\n      <string>Share</string>\n     </property>\n     <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n      <item>\n       <widget class=\"QPushButton\" name=\"copy_links\">\n        <property name=\"text\">\n         <string>Copy profile share links</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QPushButton\" name=\"copy_links_nkr\">\n        <property name=\"text\">\n         <string>Copy profile share links (Neko Links)</string>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n     <property name=\"standardButtons\">\n      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>MyLineEdit</class>\n   <extends>QLineEdit</extends>\n   <header>ui/widget/MyLineEdit.h</header>\n  </customwidget>\n </customwidgets>\n <tabstops>\n  <tabstop>name</tabstop>\n  <tabstop>type</tabstop>\n  <tabstop>front_proxy</tabstop>\n  <tabstop>front_proxy_clear</tabstop>\n  <tabstop>manually_column_width</tabstop>\n  <tabstop>archive</tabstop>\n  <tabstop>url</tabstop>\n  <tabstop>skip_auto_update</tabstop>\n  <tabstop>copy_links</tabstop>\n  <tabstop>copy_links_nkr</tabstop>\n </tabstops>\n <resources/>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>rejected()</signal>\n   <receiver>DialogEditGroup</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>199</x>\n     <y>275</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>199</x>\n     <y>149</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>accepted()</signal>\n   <receiver>DialogEditGroup</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>199</x>\n     <y>291</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>199</x>\n     <y>157</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "ui/edit/dialog_edit_profile.cpp",
    "content": "#include \"dialog_edit_profile.h\"\n#include \"ui_dialog_edit_profile.h\"\n\n#include \"ui/edit/edit_socks_http.h\"\n#include \"ui/edit/edit_shadowsocks.h\"\n#include \"ui/edit/edit_chain.h\"\n#include \"ui/edit/edit_vmess.h\"\n#include \"ui/edit/edit_trojan_vless.h\"\n#include \"ui/edit/edit_naive.h\"\n#include \"ui/edit/edit_quic.h\"\n#include \"ui/edit/edit_custom.h\"\n\n#include \"fmt/includes.h\"\n#include \"fmt/Preset.hpp\"\n\n#include \"3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp\"\n#include \"main/GuiUtils.hpp\"\n\n#include <QInputDialog>\n\n#define ADJUST_SIZE runOnUiThread([=] { adjustSize(); adjustPosition(mainwindow); }, this);\n#define LOAD_TYPE(a) ui->type->addItem(NekoGui::ProfileManager::NewProxyEntity(a)->bean->DisplayType(), a);\n\nDialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId, QWidget *parent)\n    : QDialog(parent), ui(new Ui::DialogEditProfile) {\n    // setup UI\n    ui->setupUi(this);\n    ui->dialog_layout->setAlignment(ui->left, Qt::AlignTop);\n\n    // network changed\n    network_title_base = ui->network_box->title();\n    connect(ui->network, &QComboBox::currentTextChanged, this, [=](const QString &txt) {\n        ui->network_box->setTitle(network_title_base.arg(txt));\n        // 传输设置\n        if (txt == \"tcp\") {\n            ui->header_type->setVisible(true);\n            ui->header_type_l->setVisible(true);\n            ui->path->setVisible(true);\n            ui->path_l->setVisible(true);\n            ui->host->setVisible(true);\n            ui->host_l->setVisible(true);\n        } else if (txt == \"grpc\") {\n            ui->header_type->setVisible(false);\n            ui->header_type_l->setVisible(false);\n            ui->path->setVisible(true);\n            ui->path_l->setVisible(true);\n            ui->host->setVisible(false);\n            ui->host_l->setVisible(false);\n        } else if (txt == \"ws\" || txt == \"http\" || txt == \"httpupgrade\") {\n            ui->header_type->setVisible(false);\n            ui->header_type_l->setVisible(false);\n            ui->path->setVisible(true);\n            ui->path_l->setVisible(true);\n            ui->host->setVisible(true);\n            ui->host_l->setVisible(true);\n        } else {\n            ui->header_type->setVisible(false);\n            ui->header_type_l->setVisible(false);\n            ui->path->setVisible(false);\n            ui->path_l->setVisible(false);\n            ui->host->setVisible(false);\n            ui->host_l->setVisible(false);\n        }\n        // 传输设置 ED\n        if (txt == \"ws\") {\n            ui->ws_early_data_length->setVisible(true);\n            ui->ws_early_data_length_l->setVisible(true);\n            ui->ws_early_data_name->setVisible(true);\n            ui->ws_early_data_name_l->setVisible(true);\n        } else {\n            ui->ws_early_data_length->setVisible(false);\n            ui->ws_early_data_length_l->setVisible(false);\n            ui->ws_early_data_name->setVisible(false);\n            ui->ws_early_data_name_l->setVisible(false);\n        }\n        // 传输设置 for NekoBox\n        if (!ui->utlsFingerprint->count()) ui->utlsFingerprint->addItems(Preset::SingBox::UtlsFingerPrint);\n        // 传输设置 是否可见\n        int networkBoxVisible = 0;\n        for (auto label: ui->network_box->findChildren<QLabel *>()) {\n            if (!label->isHidden()) networkBoxVisible++;\n        }\n        ui->network_box->setVisible(networkBoxVisible);\n        ADJUST_SIZE\n    });\n    ui->network->removeItem(0);\n\n    // security changed\n    connect(ui->security, &QComboBox::currentTextChanged, this, [=](const QString &txt) {\n        if (txt == \"tls\") {\n            ui->security_box->setVisible(true);\n            ui->tls_camouflage_box->setVisible(true);\n            ui->reality_spx->hide();\n            ui->reality_spx_l->hide();\n        } else {\n            ui->security_box->setVisible(false);\n            ui->tls_camouflage_box->setVisible(false);\n        }\n        ADJUST_SIZE\n    });\n    emit ui->security->currentTextChanged(ui->security->currentText());\n\n    // 确定模式和 ent\n    newEnt = _type != \"\";\n    if (newEnt) {\n        this->groupId = profileOrGroupId;\n        this->type = _type;\n\n        // load type to combo box\n        LOAD_TYPE(\"socks\")\n        LOAD_TYPE(\"http\")\n        LOAD_TYPE(\"shadowsocks\")\n        LOAD_TYPE(\"trojan\")\n        LOAD_TYPE(\"vmess\")\n        LOAD_TYPE(\"vless\")\n        LOAD_TYPE(\"naive\")\n        LOAD_TYPE(\"hysteria2\")\n        LOAD_TYPE(\"tuic\")\n        ui->type->addItem(tr(\"Custom (%1 outbound)\").arg(software_core_name), \"internal\");\n        ui->type->addItem(tr(\"Custom (%1 config)\").arg(software_core_name), \"internal-full\");\n        ui->type->addItem(tr(\"Custom (Extra Core)\"), \"custom\");\n        LOAD_TYPE(\"chain\")\n\n        // type changed\n        connect(ui->type, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) {\n            typeSelected(ui->type->itemData(index).toString());\n        });\n\n        ui->apply_to_group->hide();\n    } else {\n        this->ent = NekoGui::profileManager->GetProfile(profileOrGroupId);\n        if (this->ent == nullptr) return;\n        this->type = ent->type;\n        ui->type->setVisible(false);\n        ui->type_l->setVisible(false);\n    }\n\n    typeSelected(this->type);\n}\n\nDialogEditProfile::~DialogEditProfile() {\n    delete ui;\n}\n\nvoid DialogEditProfile::typeSelected(const QString &newType) {\n    QString customType;\n    type = newType;\n    bool validType = true;\n\n    if (type == \"socks\" || type == \"http\") {\n        auto _innerWidget = new EditSocksHttp(this);\n        innerWidget = _innerWidget;\n        innerEditor = _innerWidget;\n    } else if (type == \"shadowsocks\") {\n        auto _innerWidget = new EditShadowSocks(this);\n        innerWidget = _innerWidget;\n        innerEditor = _innerWidget;\n    } else if (type == \"chain\") {\n        auto _innerWidget = new EditChain(this);\n        innerWidget = _innerWidget;\n        innerEditor = _innerWidget;\n    } else if (type == \"vmess\") {\n        auto _innerWidget = new EditVMess(this);\n        innerWidget = _innerWidget;\n        innerEditor = _innerWidget;\n    } else if (type == \"trojan\" || type == \"vless\") {\n        auto _innerWidget = new EditTrojanVLESS(this);\n        innerWidget = _innerWidget;\n        innerEditor = _innerWidget;\n    } else if (type == \"naive\") {\n        auto _innerWidget = new EditNaive(this);\n        innerWidget = _innerWidget;\n        innerEditor = _innerWidget;\n    } else if (type == \"hysteria2\" || type == \"tuic\") {\n        auto _innerWidget = new EditQUIC(this);\n        innerWidget = _innerWidget;\n        innerEditor = _innerWidget;\n    } else if (type == \"custom\" || type == \"internal\" || type == \"internal-full\") {\n        auto _innerWidget = new EditCustom(this);\n        innerWidget = _innerWidget;\n        innerEditor = _innerWidget;\n        customType = newEnt ? type : ent->CustomBean()->core;\n        if (customType != \"custom\") _innerWidget->preset_core = customType;\n        type = \"custom\";\n    } else {\n        validType = false;\n    }\n\n    if (!validType) {\n        MessageBoxWarning(newType, \"Wrong type\");\n        return;\n    }\n\n    if (newEnt) {\n        this->ent = NekoGui::ProfileManager::NewProxyEntity(type);\n        this->ent->gid = groupId;\n    }\n\n    // hide some widget\n    auto showAddressPort = type != \"chain\" && customType != \"internal\" && customType != \"internal-full\";\n    ui->address->setVisible(showAddressPort);\n    ui->address_l->setVisible(showAddressPort);\n    ui->port->setVisible(showAddressPort);\n    ui->port_l->setVisible(showAddressPort);\n\n    // 右边 stream\n    auto stream = GetStreamSettings(ent->bean.get());\n    if (stream != nullptr) {\n        ui->right_all_w->setVisible(true);\n        ui->network->setCurrentText(stream->network);\n        ui->security->setCurrentText(stream->security);\n        ui->packet_encoding->setCurrentText(stream->packet_encoding);\n        ui->path->setText(stream->path);\n        ui->host->setText(stream->host);\n        ui->sni->setText(stream->sni);\n        ui->alpn->setText(stream->alpn);\n        if (newEnt) {\n            ui->utlsFingerprint->setCurrentText(NekoGui::dataStore->utlsFingerprint);\n        } else {\n            ui->utlsFingerprint->setCurrentText(stream->utlsFingerprint);\n        }\n        ui->insecure->setChecked(stream->allow_insecure);\n        ui->header_type->setCurrentText(stream->header_type);\n        ui->ws_early_data_name->setText(stream->ws_early_data_name);\n        ui->ws_early_data_length->setText(Int2String(stream->ws_early_data_length));\n        ui->reality_pbk->setText(stream->reality_pbk);\n        ui->reality_sid->setText(stream->reality_sid);\n        ui->multiplex->setCurrentIndex(stream->multiplex_status);\n        CACHE.certificate = stream->certificate;\n    } else {\n        ui->right_all_w->setVisible(false);\n    }\n\n    // left: custom\n    CACHE.custom_config = ent->bean->custom_config;\n    CACHE.custom_outbound = ent->bean->custom_outbound;\n    bool show_custom_config = true;\n    bool show_custom_outbound = true;\n    if (type == \"chain\") {\n        show_custom_outbound = false;\n    } else if (type == \"custom\") {\n        if (customType == \"internal\") {\n            show_custom_outbound = false;\n        } else if (customType == \"internal-full\") {\n            show_custom_outbound = false;\n            show_custom_config = false;\n        }\n    }\n    ui->custom_box->setVisible(show_custom_outbound);\n    ui->custom_global_box->setVisible(show_custom_config);\n\n    // 左边 bean\n    auto old = ui->bean->layout()->itemAt(0)->widget();\n    ui->bean->layout()->removeWidget(old);\n    innerWidget->layout()->setContentsMargins(0, 0, 0, 0);\n    ui->bean->layout()->addWidget(innerWidget);\n    ui->bean->setTitle(ent->bean->DisplayType());\n    delete old;\n\n    // 左边 bean inner editor\n    innerEditor->get_edit_dialog = [&]() { return (QWidget *) this; };\n    innerEditor->get_edit_text_name = [&]() { return ui->name->text(); };\n    innerEditor->get_edit_text_serverAddress = [&]() { return ui->address->text(); };\n    innerEditor->get_edit_text_serverPort = [&]() { return ui->port->text(); };\n    innerEditor->editor_cache_updated = [=] { editor_cache_updated_impl(); };\n    innerEditor->onStart(ent);\n\n    // 左边 common\n    ui->name->setText(ent->bean->name);\n    ui->address->setText(ent->bean->serverAddress);\n    ui->port->setText(Int2String(ent->bean->serverPort));\n    ui->port->setValidator(QRegExpValidator_Number);\n\n    // 星号\n    ADD_ASTERISK(this)\n\n    // 设置 for NekoBox\n    if (type == \"vmess\" || type == \"vless\") {\n        ui->packet_encoding->setVisible(true);\n        ui->packet_encoding_l->setVisible(true);\n    } else {\n        ui->packet_encoding->setVisible(false);\n        ui->packet_encoding_l->setVisible(false);\n    }\n    if (type == \"vmess\" || type == \"vless\" || type == \"trojan\") {\n        ui->network_l->setVisible(true);\n        ui->network->setVisible(true);\n        ui->network_box->setVisible(true);\n    } else {\n        ui->network_l->setVisible(false);\n        ui->network->setVisible(false);\n        ui->network_box->setVisible(false);\n    }\n    if (type == \"vmess\" || type == \"vless\" || type == \"trojan\" || type == \"http\") {\n        ui->security->setVisible(true);\n        ui->security_l->setVisible(true);\n    } else {\n        ui->security->setVisible(false);\n        ui->security_l->setVisible(false);\n    }\n    if (type == \"vmess\" || type == \"vless\" || type == \"trojan\" || type == \"shadowsocks\") {\n        ui->multiplex->setVisible(true);\n        ui->multiplex_l->setVisible(true);\n    } else {\n        ui->multiplex->setVisible(false);\n        ui->multiplex_l->setVisible(false);\n    }\n\n    // 设置 是否可见\n    int streamBoxVisible = 0;\n    for (auto label: ui->stream_box->findChildren<QLabel *>()) {\n        if (!label->isHidden()) streamBoxVisible++;\n    }\n    ui->stream_box->setVisible(streamBoxVisible);\n\n    // 载入 type 之后，有些类型没有右边的设置\n    auto rightNoBox = (ui->stream_box->isHidden() && ui->network_box->isHidden() && ui->security_box->isHidden());\n    if (rightNoBox && !ui->right_all_w->isHidden()) {\n        ui->right_all_w->setVisible(false);\n    }\n\n    editor_cache_updated_impl();\n    ADJUST_SIZE\n\n    // 第一次显示\n    if (isHidden()) {\n        runOnUiThread([=] { show(); }, this);\n    }\n}\n\nbool DialogEditProfile::onEnd() {\n    // bean\n    if (!innerEditor->onEnd()) {\n        return false;\n    }\n\n    // 左边\n    ent->bean->name = ui->name->text();\n    ent->bean->serverAddress = ui->address->text().remove(' ');\n    ent->bean->serverPort = ui->port->text().toInt();\n\n    // 右边 stream\n    auto stream = GetStreamSettings(ent->bean.get());\n    if (stream != nullptr) {\n        stream->network = ui->network->currentText();\n        stream->security = ui->security->currentText();\n        stream->packet_encoding = ui->packet_encoding->currentText();\n        stream->path = ui->path->text();\n        stream->host = ui->host->text();\n        stream->sni = ui->sni->text();\n        stream->alpn = ui->alpn->text();\n        stream->utlsFingerprint = ui->utlsFingerprint->currentText();\n        stream->allow_insecure = ui->insecure->isChecked();\n        stream->header_type = ui->header_type->currentText();\n        stream->ws_early_data_name = ui->ws_early_data_name->text();\n        stream->ws_early_data_length = ui->ws_early_data_length->text().toInt();\n        stream->reality_pbk = ui->reality_pbk->text();\n        stream->reality_sid = ui->reality_sid->text();\n        stream->multiplex_status = ui->multiplex->currentIndex();\n        stream->certificate = CACHE.certificate;\n    }\n\n    // cached custom\n    ent->bean->custom_outbound = CACHE.custom_outbound;\n    ent->bean->custom_config = CACHE.custom_config;\n\n    return true;\n}\n\nvoid DialogEditProfile::accept() {\n    // save to ent\n    if (!onEnd()) {\n        return;\n    }\n\n    // finish\n    QStringList msg = {\"accept\"};\n\n    if (newEnt) {\n        auto ok = NekoGui::profileManager->AddProfile(ent);\n        if (!ok) {\n            MessageBoxWarning(\"???\", \"id exists\");\n        }\n    } else {\n        auto changed = ent->Save();\n        if (changed && NekoGui::dataStore->started_id == ent->id) msg << \"restart\";\n    }\n\n    MW_dialog_message(Dialog_DialogEditProfile, msg.join(\",\"));\n    QDialog::accept();\n}\n\n// cached editor (dialog)\n\nvoid DialogEditProfile::editor_cache_updated_impl() {\n    if (CACHE.certificate.isEmpty()) {\n        ui->certificate_edit->setText(tr(\"Not set\"));\n    } else {\n        ui->certificate_edit->setText(tr(\"Already set\"));\n    }\n    if (CACHE.custom_outbound.isEmpty()) {\n        ui->custom_outbound_edit->setText(tr(\"Not set\"));\n    } else {\n        ui->custom_outbound_edit->setText(tr(\"Already set\"));\n    }\n    if (CACHE.custom_config.isEmpty()) {\n        ui->custom_config_edit->setText(tr(\"Not set\"));\n    } else {\n        ui->custom_config_edit->setText(tr(\"Already set\"));\n    }\n\n    // CACHE macro\n    for (auto a: innerEditor->get_editor_cached()) {\n        if (a.second.isEmpty()) {\n            a.first->setText(tr(\"Not set\"));\n        } else {\n            a.first->setText(tr(\"Already set\"));\n        }\n    }\n}\n\nvoid DialogEditProfile::on_custom_outbound_edit_clicked() {\n    C_EDIT_JSON_ALLOW_EMPTY(custom_outbound)\n    editor_cache_updated_impl();\n}\n\nvoid DialogEditProfile::on_custom_config_edit_clicked() {\n    C_EDIT_JSON_ALLOW_EMPTY(custom_config)\n    editor_cache_updated_impl();\n}\n\nvoid DialogEditProfile::on_certificate_edit_clicked() {\n    bool ok;\n    auto txt = QInputDialog::getMultiLineText(this, tr(\"Certificate\"), \"\", CACHE.certificate, &ok);\n    if (ok) {\n        CACHE.certificate = txt;\n        editor_cache_updated_impl();\n    }\n}\n\nvoid DialogEditProfile::on_apply_to_group_clicked() {\n    if (apply_to_group_ui.empty()) {\n        apply_to_group_ui[ui->multiplex] = new FloatCheckBox(ui->multiplex, this);\n        apply_to_group_ui[ui->sni] = new FloatCheckBox(ui->sni, this);\n        apply_to_group_ui[ui->alpn] = new FloatCheckBox(ui->alpn, this);\n        apply_to_group_ui[ui->host] = new FloatCheckBox(ui->host, this);\n        apply_to_group_ui[ui->path] = new FloatCheckBox(ui->path, this);\n        apply_to_group_ui[ui->utlsFingerprint] = new FloatCheckBox(ui->utlsFingerprint, this);\n        apply_to_group_ui[ui->insecure] = new FloatCheckBox(ui->insecure, this);\n        apply_to_group_ui[ui->certificate_edit] = new FloatCheckBox(ui->certificate_edit, this);\n        apply_to_group_ui[ui->custom_config_edit] = new FloatCheckBox(ui->custom_config_edit, this);\n        apply_to_group_ui[ui->custom_outbound_edit] = new FloatCheckBox(ui->custom_outbound_edit, this);\n        ui->apply_to_group->setText(tr(\"Confirm\"));\n    } else {\n        auto group = NekoGui::profileManager->GetGroup(ent->gid);\n        if (group == nullptr) {\n            MessageBoxWarning(\"failed\", \"unknown group\");\n            return;\n        }\n        // save this\n        if (onEnd()) {\n            ent->Save();\n        } else {\n            MessageBoxWarning(\"failed\", \"failed to save\");\n            return;\n        }\n        // copy keys\n        for (const auto &pair: apply_to_group_ui) {\n            if (pair.second->isChecked()) {\n                do_apply_to_group(group, pair.first);\n            }\n            delete pair.second;\n        }\n        apply_to_group_ui.clear();\n        ui->apply_to_group->setText(tr(\"Apply settings to this group\"));\n    }\n}\n\nvoid DialogEditProfile::do_apply_to_group(const std::shared_ptr<NekoGui::Group> &group, QWidget *key) {\n    auto stream = GetStreamSettings(ent->bean.get());\n\n    auto copyStream = [=](void *p) {\n        for (const auto &profile: group->Profiles()) {\n            auto newStream = GetStreamSettings(profile->bean.get());\n            if (newStream == nullptr) continue;\n            if (stream == newStream) continue;\n            newStream->_setValue(stream->_name(p), p);\n            // qDebug() << newStream->ToJsonBytes();\n            profile->Save();\n        }\n    };\n\n    auto copyBean = [=](void *p) {\n        for (const auto &profile: group->Profiles()) {\n            if (profile == ent) continue;\n            profile->bean->_setValue(ent->bean->_name(p), p);\n            // qDebug() << profile->bean->ToJsonBytes();\n            profile->Save();\n        }\n    };\n\n    if (key == ui->multiplex) {\n        copyStream(&stream->multiplex_status);\n    } else if (key == ui->sni) {\n        copyStream(&stream->sni);\n    } else if (key == ui->alpn) {\n        copyStream(&stream->alpn);\n    } else if (key == ui->host) {\n        copyStream(&stream->host);\n    } else if (key == ui->path) {\n        copyStream(&stream->path);\n    } else if (key == ui->utlsFingerprint) {\n        copyStream(&stream->utlsFingerprint);\n    } else if (key == ui->insecure) {\n        copyStream(&stream->allow_insecure);\n    } else if (key == ui->certificate_edit) {\n        copyStream(&stream->certificate);\n    } else if (key == ui->custom_config_edit) {\n        copyBean(&ent->bean->custom_config);\n    } else if (key == ui->custom_outbound_edit) {\n        copyBean(&ent->bean->custom_outbound);\n    }\n}\n"
  },
  {
    "path": "ui/edit/dialog_edit_profile.h",
    "content": "#ifndef DIALOG_EDIT_PROFILE_H\n#define DIALOG_EDIT_PROFILE_H\n\n#include <QDialog>\n#include \"db/Database.hpp\"\n#include \"profile_editor.h\"\n\n#include \"ui/widget/FloatCheckBox.h\"\n\nnamespace Ui {\n    class DialogEditProfile;\n}\n\nclass DialogEditProfile : public QDialog {\n    Q_OBJECT\n\npublic:\n    explicit DialogEditProfile(const QString &_type, int profileOrGroupId, QWidget *parent = nullptr);\n\n    ~DialogEditProfile() override;\n\npublic slots:\n\n    void accept() override;\n\nprivate slots:\n\n    void on_custom_outbound_edit_clicked();\n\n    void on_custom_config_edit_clicked();\n\n    void on_certificate_edit_clicked();\n\n    void on_apply_to_group_clicked();\n\nprivate:\n    Ui::DialogEditProfile *ui;\n\n    std::map<QWidget *, FloatCheckBox *> apply_to_group_ui;\n\n    QWidget *innerWidget{};\n    ProfileEditor *innerEditor{};\n\n    QString type;\n    int groupId;\n    bool newEnt = false;\n    std::shared_ptr<NekoGui::ProxyEntity> ent;\n\n    QString network_title_base;\n\n    struct {\n        QString custom_outbound;\n        QString custom_config;\n        QString certificate;\n    } CACHE;\n\n    void typeSelected(const QString &newType);\n\n    bool onEnd();\n\n    void editor_cache_updated_impl();\n\n    void do_apply_to_group(const std::shared_ptr<NekoGui::Group> &group, QWidget *key);\n};\n\n#endif // DIALOG_EDIT_PROFILE_H\n"
  },
  {
    "path": "ui/edit/dialog_edit_profile.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>DialogEditProfile</class>\n <widget class=\"QDialog\" name=\"DialogEditProfile\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>1000</width>\n    <height>802</height>\n   </rect>\n  </property>\n  <property name=\"sizePolicy\">\n   <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n    <horstretch>0</horstretch>\n    <verstretch>0</verstretch>\n   </sizepolicy>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Edit</string>\n  </property>\n  <layout class=\"QHBoxLayout\" name=\"dialog_layout\">\n   <item>\n    <widget class=\"QWidget\" name=\"left_w\" native=\"true\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Maximum\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <property name=\"minimumSize\">\n      <size>\n       <width>400</width>\n       <height>0</height>\n      </size>\n     </property>\n     <layout class=\"QVBoxLayout\" name=\"left\">\n      <property name=\"sizeConstraint\">\n       <enum>QLayout::SetDefaultConstraint</enum>\n      </property>\n      <item>\n       <widget class=\"QGroupBox\" name=\"groupBox\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"title\">\n         <string>Common</string>\n        </property>\n        <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\">\n         <item>\n          <layout class=\"QGridLayout\" name=\"gridLayout_2\">\n           <item row=\"2\" column=\"1\">\n            <widget class=\"MyLineEdit\" name=\"address\"/>\n           </item>\n           <item row=\"1\" column=\"1\">\n            <widget class=\"MyLineEdit\" name=\"name\"/>\n           </item>\n           <item row=\"3\" column=\"1\">\n            <widget class=\"QLineEdit\" name=\"port\"/>\n           </item>\n           <item row=\"0\" column=\"0\">\n            <widget class=\"QLabel\" name=\"type_l\">\n             <property name=\"text\">\n              <string>Type</string>\n             </property>\n            </widget>\n           </item>\n           <item row=\"3\" column=\"0\">\n            <widget class=\"QLabel\" name=\"port_l\">\n             <property name=\"text\">\n              <string>Port</string>\n             </property>\n            </widget>\n           </item>\n           <item row=\"2\" column=\"0\">\n            <widget class=\"QLabel\" name=\"address_l\">\n             <property name=\"text\">\n              <string>Address</string>\n             </property>\n            </widget>\n           </item>\n           <item row=\"1\" column=\"0\">\n            <widget class=\"QLabel\" name=\"label\">\n             <property name=\"text\">\n              <string>Name</string>\n             </property>\n            </widget>\n           </item>\n           <item row=\"0\" column=\"1\">\n            <widget class=\"QComboBox\" name=\"type\"/>\n           </item>\n          </layout>\n         </item>\n        </layout>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QGroupBox\" name=\"bean\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"title\">\n         <string notr=\"true\">Bean</string>\n        </property>\n        <layout class=\"QVBoxLayout\" name=\"verticalLayout_4\">\n         <item>\n          <widget class=\"QWidget\" name=\"fake\" native=\"true\"/>\n         </item>\n        </layout>\n       </widget>\n      </item>\n      <item>\n       <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n        <item>\n         <widget class=\"QGroupBox\" name=\"custom_box\">\n          <property name=\"sizePolicy\">\n           <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n            <horstretch>0</horstretch>\n            <verstretch>0</verstretch>\n           </sizepolicy>\n          </property>\n          <property name=\"title\">\n           <string>Custom Outbound Settings</string>\n          </property>\n          <layout class=\"QVBoxLayout\" name=\"verticalLayout_5\">\n           <item>\n            <widget class=\"QPushButton\" name=\"custom_outbound_edit\">\n             <property name=\"text\">\n              <string notr=\"true\">Edit</string>\n             </property>\n            </widget>\n           </item>\n          </layout>\n         </widget>\n        </item>\n        <item>\n         <widget class=\"QGroupBox\" name=\"custom_global_box\">\n          <property name=\"title\">\n           <string>Custom Config Settings</string>\n          </property>\n          <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\n           <item>\n            <widget class=\"QPushButton\" name=\"custom_config_edit\">\n             <property name=\"text\">\n              <string notr=\"true\">Edit</string>\n             </property>\n            </widget>\n           </item>\n          </layout>\n         </widget>\n        </item>\n       </layout>\n      </item>\n      <item>\n       <layout class=\"QHBoxLayout\" name=\"horizontalLayout_4\">\n        <item>\n         <widget class=\"QPushButton\" name=\"apply_to_group\">\n          <property name=\"text\">\n           <string>Apply settings to this group</string>\n          </property>\n         </widget>\n        </item>\n        <item>\n         <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n          <property name=\"sizePolicy\">\n           <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\n            <horstretch>0</horstretch>\n            <verstretch>0</verstretch>\n           </sizepolicy>\n          </property>\n          <property name=\"orientation\">\n           <enum>Qt::Horizontal</enum>\n          </property>\n          <property name=\"standardButtons\">\n           <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>\n          </property>\n         </widget>\n        </item>\n       </layout>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QWidget\" name=\"right_all_w\" native=\"true\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <property name=\"minimumSize\">\n      <size>\n       <width>400</width>\n       <height>0</height>\n      </size>\n     </property>\n     <property name=\"toolTip\">\n      <string/>\n     </property>\n     <layout class=\"QVBoxLayout\" name=\"right_layout\">\n      <property name=\"sizeConstraint\">\n       <enum>QLayout::SetDefaultConstraint</enum>\n      </property>\n      <item>\n       <widget class=\"QGroupBox\" name=\"stream_box\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"title\">\n         <string>Settings</string>\n        </property>\n        <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n         <item>\n          <layout class=\"QGridLayout\" name=\"_2\">\n           <item row=\"0\" column=\"1\">\n            <widget class=\"QComboBox\" name=\"network\">\n             <property name=\"sizePolicy\">\n              <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\n               <horstretch>0</horstretch>\n               <verstretch>0</verstretch>\n              </sizepolicy>\n             </property>\n             <item>\n              <property name=\"text\">\n               <string notr=\"true\"/>\n              </property>\n             </item>\n             <item>\n              <property name=\"text\">\n               <string notr=\"true\">tcp</string>\n              </property>\n             </item>\n             <item>\n              <property name=\"text\">\n               <string notr=\"true\">ws</string>\n              </property>\n             </item>\n             <item>\n              <property name=\"text\">\n               <string notr=\"true\">httpupgrade</string>\n              </property>\n             </item>\n             <item>\n              <property name=\"text\">\n               <string notr=\"true\">http</string>\n              </property>\n             </item>\n             <item>\n              <property name=\"text\">\n               <string notr=\"true\">grpc</string>\n              </property>\n             </item>\n             <item>\n              <property name=\"text\">\n               <string notr=\"true\">quic</string>\n              </property>\n             </item>\n            </widget>\n           </item>\n           <item row=\"2\" column=\"1\">\n            <widget class=\"QComboBox\" name=\"packet_encoding\">\n             <item>\n              <property name=\"text\">\n               <string notr=\"true\"/>\n              </property>\n             </item>\n             <item>\n              <property name=\"text\">\n               <string notr=\"true\">packetaddr</string>\n              </property>\n             </item>\n             <item>\n              <property name=\"text\">\n               <string notr=\"true\">xudp</string>\n              </property>\n             </item>\n            </widget>\n           </item>\n           <item row=\"0\" column=\"0\">\n            <widget class=\"QLabel\" name=\"network_l\">\n             <property name=\"toolTip\">\n              <string>The underlying transport method. It must be consistent with the server, otherwise, the connection cannot be established.</string>\n             </property>\n             <property name=\"text\">\n              <string>Network</string>\n             </property>\n            </widget>\n           </item>\n           <item row=\"1\" column=\"1\">\n            <widget class=\"QComboBox\" name=\"security\">\n             <item>\n              <property name=\"text\">\n               <string notr=\"true\"/>\n              </property>\n             </item>\n             <item>\n              <property name=\"text\">\n               <string notr=\"true\">tls</string>\n              </property>\n             </item>\n            </widget>\n           </item>\n           <item row=\"1\" column=\"0\">\n            <widget class=\"QLabel\" name=\"security_l\">\n             <property name=\"toolTip\">\n              <string>Transport Layer Security. It must be consistent with the server, otherwise, the connection cannot be established.</string>\n             </property>\n             <property name=\"text\">\n              <string>Security</string>\n             </property>\n            </widget>\n           </item>\n           <item row=\"2\" column=\"0\">\n            <widget class=\"QLabel\" name=\"packet_encoding_l\">\n             <property name=\"toolTip\">\n              <string>UDP FullCone Packet encoding for implementing features such as UDP FullCone. Server support is required, if the wrong selection is made, the connection cannot be made. Please leave it blank.</string>\n             </property>\n             <property name=\"text\">\n              <string>Packet Encoding</string>\n             </property>\n            </widget>\n           </item>\n           <item row=\"3\" column=\"0\">\n            <widget class=\"QLabel\" name=\"multiplex_l\">\n             <property name=\"toolTip\">\n              <string>Server support is required</string>\n             </property>\n             <property name=\"text\">\n              <string>Multiplex</string>\n             </property>\n            </widget>\n           </item>\n           <item row=\"3\" column=\"1\">\n            <widget class=\"QComboBox\" name=\"multiplex\">\n             <item>\n              <property name=\"text\">\n               <string>Keep Default</string>\n              </property>\n             </item>\n             <item>\n              <property name=\"text\">\n               <string>On</string>\n              </property>\n             </item>\n             <item>\n              <property name=\"text\">\n               <string>Off</string>\n              </property>\n             </item>\n            </widget>\n           </item>\n          </layout>\n         </item>\n        </layout>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QGroupBox\" name=\"network_box\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"title\">\n         <string>Network Settings (%1)</string>\n        </property>\n        <layout class=\"QGridLayout\" name=\"gridLayout\">\n         <item row=\"1\" column=\"0\">\n          <widget class=\"QLabel\" name=\"path_l\">\n           <property name=\"toolTip\">\n            <string notr=\"true\">http path (ws/http/伪装http)\nserviceName (gRPC)\nkey (QUIC)</string>\n           </property>\n           <property name=\"text\">\n            <string notr=\"true\">Path</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"2\" column=\"1\">\n          <widget class=\"MyLineEdit\" name=\"host\"/>\n         </item>\n         <item row=\"2\" column=\"0\">\n          <widget class=\"QLabel\" name=\"host_l\">\n           <property name=\"toolTip\">\n            <string notr=\"true\">http host (ws/http/伪装http)\nsecurity (QUIC)</string>\n           </property>\n           <property name=\"text\">\n            <string notr=\"true\">Host</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"0\" column=\"0\">\n          <widget class=\"QLabel\" name=\"header_type_l\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"toolTip\">\n            <string notr=\"true\">伪装头部类型 (tcp/quic)</string>\n           </property>\n           <property name=\"text\">\n            <string notr=\"true\">header</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"1\" column=\"1\">\n          <widget class=\"MyLineEdit\" name=\"path\"/>\n         </item>\n         <item row=\"0\" column=\"1\">\n          <widget class=\"QComboBox\" name=\"header_type\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Fixed\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"editable\">\n            <bool>true</bool>\n           </property>\n           <item>\n            <property name=\"text\">\n             <string notr=\"true\"/>\n            </property>\n           </item>\n           <item>\n            <property name=\"text\">\n             <string notr=\"true\">http</string>\n            </property>\n           </item>\n          </widget>\n         </item>\n         <item row=\"3\" column=\"1\">\n          <widget class=\"MyLineEdit\" name=\"ws_early_data_length\"/>\n         </item>\n         <item row=\"3\" column=\"0\">\n          <widget class=\"QLabel\" name=\"ws_early_data_length_l\">\n           <property name=\"text\">\n            <string notr=\"true\">EarlyData Length</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"4\" column=\"1\">\n          <widget class=\"MyLineEdit\" name=\"ws_early_data_name\"/>\n         </item>\n         <item row=\"4\" column=\"0\">\n          <widget class=\"QLabel\" name=\"ws_early_data_name_l\">\n           <property name=\"text\">\n            <string notr=\"true\">EarlyData Name</string>\n           </property>\n          </widget>\n         </item>\n        </layout>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QGroupBox\" name=\"security_box\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"title\">\n         <string>TLS Security Settings</string>\n        </property>\n        <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n         <item>\n          <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n           <item>\n            <widget class=\"QCheckBox\" name=\"insecure\">\n             <property name=\"sizePolicy\">\n              <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Fixed\">\n               <horstretch>0</horstretch>\n               <verstretch>0</verstretch>\n              </sizepolicy>\n             </property>\n             <property name=\"toolTip\">\n              <string>When enabled, V2Ray will not check the validity of the TLS certificate provided by the remote host (the security is equivalent to plaintext)</string>\n             </property>\n             <property name=\"text\">\n              <string>Allow insecure</string>\n             </property>\n            </widget>\n           </item>\n           <item>\n            <widget class=\"Line\" name=\"line\">\n             <property name=\"orientation\">\n              <enum>Qt::Vertical</enum>\n             </property>\n            </widget>\n           </item>\n           <item>\n            <widget class=\"QLabel\" name=\"label_6\">\n             <property name=\"sizePolicy\">\n              <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n               <horstretch>0</horstretch>\n               <verstretch>0</verstretch>\n              </sizepolicy>\n             </property>\n             <property name=\"toolTip\">\n              <string notr=\"true\"/>\n             </property>\n             <property name=\"text\">\n              <string>Certificate</string>\n             </property>\n            </widget>\n           </item>\n           <item>\n            <widget class=\"QPushButton\" name=\"certificate_edit\">\n             <property name=\"text\">\n              <string notr=\"true\">Edit</string>\n             </property>\n            </widget>\n           </item>\n          </layout>\n         </item>\n         <item>\n          <layout class=\"QGridLayout\" name=\"_3\">\n           <item row=\"0\" column=\"1\">\n            <widget class=\"MyLineEdit\" name=\"sni\"/>\n           </item>\n           <item row=\"0\" column=\"0\">\n            <widget class=\"QLabel\" name=\"label_5\">\n             <property name=\"toolTip\">\n              <string>Server name indication, clear text.</string>\n             </property>\n             <property name=\"text\">\n              <string notr=\"true\">SNI</string>\n             </property>\n            </widget>\n           </item>\n           <item row=\"1\" column=\"0\">\n            <widget class=\"QLabel\" name=\"label_8\">\n             <property name=\"toolTip\">\n              <string>Application layer protocol negotiation, clear text. Please separate them with commas.</string>\n             </property>\n             <property name=\"text\">\n              <string notr=\"true\">ALPN</string>\n             </property>\n            </widget>\n           </item>\n           <item row=\"1\" column=\"1\">\n            <widget class=\"MyLineEdit\" name=\"alpn\"/>\n           </item>\n          </layout>\n         </item>\n        </layout>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QGroupBox\" name=\"tls_camouflage_box\">\n        <property name=\"title\">\n         <string>TLS Camouflage Settings</string>\n        </property>\n        <layout class=\"QGridLayout\" name=\"gridLayout_3\">\n         <item row=\"0\" column=\"1\">\n          <widget class=\"QComboBox\" name=\"utlsFingerprint\">\n           <property name=\"editable\">\n            <bool>true</bool>\n           </property>\n          </widget>\n         </item>\n         <item row=\"0\" column=\"0\">\n          <widget class=\"QLabel\" name=\"label_2\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string notr=\"true\">Fingerprint</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"1\" column=\"0\">\n          <widget class=\"QLabel\" name=\"reality_pbk_l\">\n           <property name=\"toolTip\">\n            <string>Reality public key. If not empty, turn TLS into REALITY.</string>\n           </property>\n           <property name=\"text\">\n            <string notr=\"true\">Reality Pbk</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"2\" column=\"0\">\n          <widget class=\"QLabel\" name=\"reality_sid_l\">\n           <property name=\"toolTip\">\n            <string>Reality short id. Accept only one value.</string>\n           </property>\n           <property name=\"text\">\n            <string notr=\"true\">Reality Sid</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"2\" column=\"1\">\n          <widget class=\"MyLineEdit\" name=\"reality_sid\"/>\n         </item>\n         <item row=\"1\" column=\"1\">\n          <widget class=\"MyLineEdit\" name=\"reality_pbk\"/>\n         </item>\n         <item row=\"3\" column=\"0\">\n          <widget class=\"QLabel\" name=\"reality_spx_l\">\n           <property name=\"toolTip\">\n            <string notr=\"true\"/>\n           </property>\n           <property name=\"text\">\n            <string notr=\"true\">SpiderX</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"3\" column=\"1\">\n          <widget class=\"MyLineEdit\" name=\"reality_spx\"/>\n         </item>\n        </layout>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>MyLineEdit</class>\n   <extends>QLineEdit</extends>\n   <header>ui/widget/MyLineEdit.h</header>\n  </customwidget>\n </customwidgets>\n <tabstops>\n  <tabstop>type</tabstop>\n  <tabstop>name</tabstop>\n  <tabstop>address</tabstop>\n  <tabstop>port</tabstop>\n  <tabstop>custom_outbound_edit</tabstop>\n  <tabstop>custom_config_edit</tabstop>\n  <tabstop>apply_to_group</tabstop>\n  <tabstop>network</tabstop>\n  <tabstop>security</tabstop>\n  <tabstop>packet_encoding</tabstop>\n  <tabstop>multiplex</tabstop>\n  <tabstop>header_type</tabstop>\n  <tabstop>path</tabstop>\n  <tabstop>host</tabstop>\n  <tabstop>ws_early_data_length</tabstop>\n  <tabstop>ws_early_data_name</tabstop>\n  <tabstop>insecure</tabstop>\n  <tabstop>certificate_edit</tabstop>\n  <tabstop>sni</tabstop>\n  <tabstop>alpn</tabstop>\n  <tabstop>utlsFingerprint</tabstop>\n  <tabstop>reality_pbk</tabstop>\n  <tabstop>reality_sid</tabstop>\n </tabstops>\n <resources/>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>accepted()</signal>\n   <receiver>DialogEditProfile</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>151</x>\n     <y>500</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>299</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>rejected()</signal>\n   <receiver>DialogEditProfile</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>151</x>\n     <y>500</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>299</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "ui/edit/edit_chain.cpp",
    "content": "#include \"edit_chain.h\"\n#include \"ui_edit_chain.h\"\n\n#include \"ui/mainwindow_interface.h\"\n#include \"ui/widget/ProxyItem.h\"\n\n#include \"db/Database.hpp\"\n#include \"fmt/ChainBean.hpp\"\n\nEditChain::EditChain(QWidget *parent) : QWidget(parent), ui(new Ui::EditChain) {\n    ui->setupUi(this);\n}\n\nEditChain::~EditChain() {\n    delete ui;\n}\n\nvoid EditChain::onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) {\n    this->ent = _ent;\n    auto bean = this->ent->ChainBean();\n\n    for (auto id: bean->list) {\n        AddProfileToListIfExist(id);\n    }\n}\n\nbool EditChain::onEnd() {\n    if (get_edit_text_name().isEmpty()) {\n        MessageBoxWarning(software_name, tr(\"Name cannot be empty.\"));\n        return false;\n    }\n\n    auto bean = this->ent->ChainBean();\n\n    QList<int> idList;\n    for (int i = 0; i < ui->listWidget->count(); i++) {\n        idList << ui->listWidget->item(i)->data(114514).toInt();\n    }\n    bean->list = idList;\n\n    return true;\n}\n\nvoid EditChain::on_select_profile_clicked() {\n    get_edit_dialog()->hide();\n    GetMainWindow()->start_select_mode(this, [=](int id) {\n        get_edit_dialog()->show();\n        AddProfileToListIfExist(id);\n    });\n}\n\nvoid EditChain::AddProfileToListIfExist(int profileId) {\n    auto _ent = NekoGui::profileManager->GetProfile(profileId);\n    if (_ent != nullptr && _ent->type != \"chain\") {\n        auto wI = new QListWidgetItem();\n        wI->setData(114514, profileId);\n        auto w = new ProxyItem(this, _ent, wI);\n        ui->listWidget->addItem(wI);\n        ui->listWidget->setItemWidget(wI, w);\n        // change button\n        connect(w->get_change_button(), &QPushButton::clicked, w, [=] {\n            get_edit_dialog()->hide();\n            GetMainWindow()->start_select_mode(w, [=](int newId) {\n                get_edit_dialog()->show();\n                ReplaceProfile(w, newId);\n            });\n        });\n    }\n}\n\nvoid EditChain::ReplaceProfile(ProxyItem *w, int profileId) {\n    auto _ent = NekoGui::profileManager->GetProfile(profileId);\n    if (_ent != nullptr && _ent->type != \"chain\") {\n        w->item->setData(114514, profileId);\n        w->ent = _ent;\n        w->refresh_data();\n    }\n}\n"
  },
  {
    "path": "ui/edit/edit_chain.h",
    "content": "#pragma once\n\n#include <QWidget>\n#include \"profile_editor.h\"\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class EditChain;\n}\nQT_END_NAMESPACE\n\nclass ProxyItem;\n\nclass EditChain : public QWidget, public ProfileEditor {\n    Q_OBJECT\n\npublic:\n    explicit EditChain(QWidget *parent = nullptr);\n\n    ~EditChain() override;\n\n    void onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) override;\n\n    bool onEnd() override;\n\nprivate:\n    Ui::EditChain *ui;\n    std::shared_ptr<NekoGui::ProxyEntity> ent;\n\n    void AddProfileToListIfExist(int profileId);\n\n    static void ReplaceProfile(ProxyItem *w, int profileId);\n\nprivate slots:\n\n    void on_select_profile_clicked();\n};\n"
  },
  {
    "path": "ui/edit/edit_chain.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>EditChain</class>\n <widget class=\"QWidget\" name=\"EditChain\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>400</width>\n    <height>400</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string notr=\"true\">EditChain</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <widget class=\"QLabel\" name=\"label\">\n     <property name=\"text\">\n      <string>Traffic order is from top to bottom</string>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QListWidget\" name=\"listWidget\">\n     <property name=\"minimumSize\">\n      <size>\n       <width>0</width>\n       <height>300</height>\n      </size>\n     </property>\n     <property name=\"contextMenuPolicy\">\n      <enum>Qt::ActionsContextMenu</enum>\n     </property>\n     <property name=\"dragDropMode\">\n      <enum>QAbstractItemView::InternalMove</enum>\n     </property>\n     <property name=\"defaultDropAction\">\n      <enum>Qt::MoveAction</enum>\n     </property>\n     <property name=\"movement\">\n      <enum>QListView::Free</enum>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"select_profile\">\n     <property name=\"text\">\n      <string>Select Profile</string>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "ui/edit/edit_custom.cpp",
    "content": "#include \"edit_custom.h\"\n#include \"ui_edit_custom.h\"\n\n#include \"3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp\"\n#include \"fmt/CustomBean.hpp\"\n#include \"fmt/Preset.hpp\"\n#include \"db/ConfigBuilder.hpp\"\n#include \"db/Database.hpp\"\n\n#include <QMessageBox>\n#include <QClipboard>\n\nEditCustom::EditCustom(QWidget *parent) : QWidget(parent), ui(new Ui::EditCustom) {\n    ui->setupUi(this);\n    ui->config_simple->setPlaceholderText(\n        \"example:\\n\"\n        \"  server-address: \\\"127.0.0.1:%mapping_port%\\\"\\n\"\n        \"  listen-address: \\\"127.0.0.1\\\"\\n\"\n        \"  listen-port: %socks_port%\\n\"\n        \"  host: your-domain.com\\n\"\n        \"  sni: your-domain.com\\n\");\n}\n\nEditCustom::~EditCustom() {\n    delete ui;\n}\n\n#define SAVE_CUSTOM_BEAN                            \\\n    P_SAVE_COMBO_STRING(core)                       \\\n    bean->command = ui->command->text().split(\" \"); \\\n    P_SAVE_STRING_PLAIN(config_simple)              \\\n    P_SAVE_COMBO_STRING(config_suffix)              \\\n    P_SAVE_INT(mapping_port)                        \\\n    P_SAVE_INT(socks_port)\n\nvoid EditCustom::onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) {\n    this->ent = _ent;\n    auto bean = this->ent->CustomBean();\n\n    // load known core\n    auto core_map = QString2QJsonObject(NekoGui::dataStore->extraCore->core_map);\n    for (const auto &key: core_map.keys()) {\n        ui->core->addItem(key);\n    }\n    if (preset_core == \"internal\") {\n        preset_command = preset_config = \"\";\n        ui->config_simple->setPlaceholderText(\n            \"{\\n\"\n            \"    \\\"type\\\": \\\"socks\\\",\\n\"\n            \"    // ...\\n\"\n            \"}\");\n    } else if (preset_core == \"internal-full\") {\n        preset_command = preset_config = \"\";\n        ui->config_simple->setPlaceholderText(\n            \"{\\n\"\n            \"    \\\"inbounds\\\": [],\\n\"\n            \"    \\\"outbounds\\\": []\\n\"\n            \"}\");\n    }\n\n    // load core ui\n    P_LOAD_COMBO_STRING(core)\n    ui->command->setText(bean->command.join(\" \"));\n    ui->config_simple->setPlainText(bean->config_simple);\n    P_LOAD_COMBO_STRING(config_suffix)\n    P_LOAD_INT(mapping_port)\n    P_LOAD_INT(socks_port)\n\n    // custom external\n    if (!bean->core.isEmpty()) {\n        ui->core->setDisabled(true);\n    } else if (!preset_core.isEmpty()) {\n        bean->core = preset_core;\n        ui->core->setDisabled(true);\n        ui->core->setCurrentText(preset_core);\n        ui->command->setText(preset_command);\n        ui->config_simple->setPlainText(preset_config);\n    }\n\n    // custom internal\n    if (preset_core == \"internal\" || preset_core == \"internal-full\") {\n        ui->core->hide();\n        if (preset_core == \"internal\") {\n            ui->core_l->setText(tr(\"Outbound JSON, please read the documentation.\"));\n        } else {\n            ui->core_l->setText(tr(\"Please fill the complete config.\"));\n        }\n        ui->w_ext1->hide();\n        ui->w_ext2->hide();\n    }\n\n    // Preview\n    connect(ui->preview, &QPushButton::clicked, this, [=] {\n        // CustomBean::BuildExternal\n        QStringList th;\n        auto mapping_port = ui->mapping_port->text().toInt();\n        auto socks_port = ui->socks_port->text().toInt();\n        th << \"%mapping_port% => \" + (mapping_port <= 0 ? \"Random\" : Int2String(mapping_port));\n        th << \"%socks_port% => \" + (socks_port <= 0 ? \"Random\" : Int2String(socks_port));\n        th << \"%server_addr% => \" + get_edit_text_serverAddress();\n        th << \"%server_port% => \" + get_edit_text_serverPort();\n        MessageBoxInfo(tr(\"Preview replace\"), th.join(\"\\n\"));\n        // EditCustom::onEnd\n        auto tmpEnt = NekoGui::ProfileManager::NewProxyEntity(\"custom\");\n        auto bean = tmpEnt->CustomBean();\n        SAVE_CUSTOM_BEAN\n        // 补充\n        bean->serverAddress = get_edit_text_serverAddress();\n        bean->serverPort = get_edit_text_serverPort().toInt();\n        if (bean->core.isEmpty()) return;\n        //\n        auto result = NekoGui::BuildConfig(tmpEnt, false, false);\n        if (!result->error.isEmpty()) {\n            MessageBoxInfo(software_name, result->error);\n            return;\n        }\n        for (const auto &extR: result->extRs) {\n            auto command = QStringList{extR->program};\n            command += extR->arguments;\n            auto btn = QMessageBox::information(this, tr(\"Preview config\"),\n                                                QStringLiteral(\"Command: %1\\n\\n%2\").arg(QStringList2Command(command), extR->config_export),\n                                                \"OK\", \"Copy\", \"\", 0, 0);\n            if (btn == 1) {\n                QApplication::clipboard()->setText(extR->config_export);\n            }\n        }\n    });\n}\n\nbool EditCustom::onEnd() {\n    if (get_edit_text_name().isEmpty()) {\n        MessageBoxWarning(software_name, tr(\"Name cannot be empty.\"));\n        return false;\n    }\n    if (ui->core->currentText().isEmpty()) {\n        MessageBoxWarning(software_name, tr(\"Please pick a core.\"));\n        return false;\n    }\n\n    auto bean = this->ent->CustomBean();\n\n    SAVE_CUSTOM_BEAN\n\n    return true;\n}\n\nvoid EditCustom::on_as_json_clicked() {\n    auto editor = new JsonEditor(QString2QJsonObject(ui->config_simple->toPlainText()), this);\n    auto result = editor->OpenEditor();\n    if (!result.isEmpty()) {\n        ui->config_simple->setPlainText(QJsonObject2QString(result, false));\n    }\n}\n"
  },
  {
    "path": "ui/edit/edit_custom.h",
    "content": "#pragma once\n\n#include <QWidget>\n#include \"profile_editor.h\"\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class EditCustom;\n}\nQT_END_NAMESPACE\n\nclass EditCustom : public QWidget, public ProfileEditor {\n    Q_OBJECT\n\npublic:\n    QString preset_core;\n    QString preset_command;\n    QString preset_config;\n\n    explicit EditCustom(QWidget *parent = nullptr);\n\n    ~EditCustom() override;\n\n    void onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) override;\n\n    bool onEnd() override;\n\nprivate:\n    Ui::EditCustom *ui;\n    std::shared_ptr<NekoGui::ProxyEntity> ent;\n\nprivate slots:\n\n    void on_as_json_clicked();\n};\n"
  },
  {
    "path": "ui/edit/edit_custom.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>EditCustom</class>\n <widget class=\"QWidget\" name=\"EditCustom\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>400</width>\n    <height>450</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string notr=\"true\">EditCustom</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <item>\n      <widget class=\"QLabel\" name=\"core_l\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"text\">\n        <string>Core</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QComboBox\" name=\"core\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"editable\">\n        <bool>true</bool>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <spacer name=\"horizontalSpacer\">\n       <property name=\"orientation\">\n        <enum>Qt::Horizontal</enum>\n       </property>\n       <property name=\"sizeHint\" stdset=\"0\">\n        <size>\n         <width>40</width>\n         <height>20</height>\n        </size>\n       </property>\n      </spacer>\n     </item>\n     <item>\n      <widget class=\"QPushButton\" name=\"as_json\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Fixed\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"text\">\n        <string>Json Editor</string>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <widget class=\"QWidget\" name=\"w_ext1\" native=\"true\">\n     <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n      <property name=\"leftMargin\">\n       <number>0</number>\n      </property>\n      <property name=\"topMargin\">\n       <number>0</number>\n      </property>\n      <property name=\"rightMargin\">\n       <number>0</number>\n      </property>\n      <property name=\"bottomMargin\">\n       <number>0</number>\n      </property>\n      <item>\n       <widget class=\"QLabel\" name=\"command_l\">\n        <property name=\"text\">\n         <string>Command</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QLineEdit\" name=\"command\">\n        <property name=\"placeholderText\">\n         <string notr=\"true\">%config%</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QLabel\" name=\"config_suffix_l\">\n        <property name=\"text\">\n         <string>Config Suffix</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QComboBox\" name=\"config_suffix\">\n        <property name=\"editable\">\n         <bool>true</bool>\n        </property>\n        <item>\n         <property name=\"text\">\n          <string notr=\"true\"/>\n         </property>\n        </item>\n        <item>\n         <property name=\"text\">\n          <string notr=\"true\">json</string>\n         </property>\n        </item>\n        <item>\n         <property name=\"text\">\n          <string notr=\"true\">yml</string>\n         </property>\n        </item>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QWidget\" name=\"w_ext2\" native=\"true\">\n     <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\n      <property name=\"leftMargin\">\n       <number>0</number>\n      </property>\n      <property name=\"topMargin\">\n       <number>0</number>\n      </property>\n      <property name=\"rightMargin\">\n       <number>0</number>\n      </property>\n      <property name=\"bottomMargin\">\n       <number>0</number>\n      </property>\n      <item>\n       <widget class=\"QLabel\" name=\"label\">\n        <property name=\"toolTip\">\n         <string>Random if it's empty or zero.</string>\n        </property>\n        <property name=\"text\">\n         <string notr=\"true\">Mapping Port</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QLineEdit\" name=\"mapping_port\"/>\n      </item>\n      <item>\n       <widget class=\"QLabel\" name=\"label_2\">\n        <property name=\"toolTip\">\n         <string>Random if it's empty or zero.</string>\n        </property>\n        <property name=\"text\">\n         <string notr=\"true\">Socks Port</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QLineEdit\" name=\"socks_port\"/>\n      </item>\n      <item>\n       <widget class=\"QPushButton\" name=\"preview\">\n        <property name=\"text\">\n         <string>Preview</string>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPlainTextEdit\" name=\"config_simple\">\n     <property name=\"minimumSize\">\n      <size>\n       <width>0</width>\n       <height>300</height>\n      </size>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <tabstops>\n  <tabstop>core</tabstop>\n  <tabstop>as_json</tabstop>\n  <tabstop>command</tabstop>\n  <tabstop>config_suffix</tabstop>\n  <tabstop>mapping_port</tabstop>\n  <tabstop>socks_port</tabstop>\n  <tabstop>preview</tabstop>\n  <tabstop>config_simple</tabstop>\n </tabstops>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "ui/edit/edit_naive.cpp",
    "content": "#include \"edit_naive.h\"\n#include \"ui_edit_naive.h\"\n\n#include \"fmt/NaiveBean.hpp\"\n\n#include <QInputDialog>\n\nEditNaive::EditNaive(QWidget *parent) : QWidget(parent), ui(new Ui::EditNaive) {\n    ui->setupUi(this);\n}\n\nEditNaive::~EditNaive() {\n    delete ui;\n}\n\nvoid EditNaive::onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) {\n    this->ent = _ent;\n    auto bean = this->ent->NaiveBean();\n\n    P_LOAD_STRING(username);\n    P_LOAD_STRING(password);\n    P_LOAD_COMBO_STRING(protocol);\n    P_C_LOAD_STRING(extra_headers);\n    P_LOAD_STRING(sni);\n    P_C_LOAD_STRING(certificate);\n    P_LOAD_INT(insecure_concurrency);\n    P_LOAD_BOOL(disable_log);\n}\n\nbool EditNaive::onEnd() {\n    auto bean = this->ent->NaiveBean();\n\n    P_SAVE_STRING(username);\n    P_SAVE_STRING(password);\n    P_SAVE_COMBO_STRING(protocol);\n    P_C_SAVE_STRING(extra_headers);\n    P_SAVE_STRING(sni);\n    P_C_SAVE_STRING(certificate);\n    P_SAVE_INT(insecure_concurrency);\n    P_SAVE_BOOL(disable_log);\n\n    return true;\n}\n\nQList<QPair<QPushButton *, QString>> EditNaive::get_editor_cached() {\n    return {\n        {ui->certificate, CACHE.certificate},\n        {ui->extra_headers, CACHE.extra_headers},\n    };\n}\n\nvoid EditNaive::on_certificate_clicked() {\n    bool ok;\n    auto txt = QInputDialog::getMultiLineText(this, tr(\"Certificate\"), \"\", CACHE.certificate, &ok);\n    if (ok) {\n        CACHE.certificate = txt;\n        editor_cache_updated();\n    }\n}\n\nvoid EditNaive::on_extra_headers_clicked() {\n    bool ok;\n    auto txt = QInputDialog::getMultiLineText(this, tr(\"Extra headers\"), \"\", CACHE.extra_headers, &ok);\n    if (ok) {\n        CACHE.extra_headers = txt;\n        editor_cache_updated();\n    }\n}\n"
  },
  {
    "path": "ui/edit/edit_naive.h",
    "content": "#pragma once\n\n#include <QWidget>\n#include \"profile_editor.h\"\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class EditNaive;\n}\nQT_END_NAMESPACE\n\nclass EditNaive : public QWidget, public ProfileEditor {\n    Q_OBJECT\n\npublic:\n    explicit EditNaive(QWidget *parent = nullptr);\n\n    ~EditNaive() override;\n\n    void onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) override;\n\n    bool onEnd() override;\n\n    QList<QPair<QPushButton *, QString>> get_editor_cached() override;\n\nprivate:\n    Ui::EditNaive *ui;\n    std::shared_ptr<NekoGui::ProxyEntity> ent;\n\n    struct {\n        QString certificate;\n        QString extra_headers;\n    } CACHE;\n\nprivate slots:\n\n    void on_certificate_clicked();\n\n    void on_extra_headers_clicked();\n};\n"
  },
  {
    "path": "ui/edit/edit_naive.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>EditNaive</class>\n <widget class=\"QWidget\" name=\"EditNaive\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>525</width>\n    <height>304</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string notr=\"true\">EditNaive</string>\n  </property>\n  <layout class=\"QGridLayout\" name=\"gridLayout\">\n   <item row=\"0\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label\">\n     <property name=\"text\">\n      <string>Username</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"0\" column=\"1\">\n    <widget class=\"MyLineEdit\" name=\"username\"/>\n   </item>\n   <item row=\"1\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_2\">\n     <property name=\"text\">\n      <string>Password</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"1\" column=\"1\">\n    <widget class=\"MyLineEdit\" name=\"password\"/>\n   </item>\n   <item row=\"2\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_3\">\n     <property name=\"text\">\n      <string>Protocol</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"2\" column=\"1\">\n    <widget class=\"QComboBox\" name=\"protocol\">\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\">https</string>\n      </property>\n     </item>\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\">quic</string>\n      </property>\n     </item>\n    </widget>\n   </item>\n   <item row=\"3\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_4\">\n     <property name=\"text\">\n      <string>Extra headers</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"3\" column=\"1\">\n    <widget class=\"QPushButton\" name=\"extra_headers\">\n     <property name=\"text\">\n      <string notr=\"true\">PushButton</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"4\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_6\">\n     <property name=\"text\">\n      <string>SNI</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"4\" column=\"1\">\n    <widget class=\"MyLineEdit\" name=\"sni\"/>\n   </item>\n   <item row=\"5\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_7\">\n     <property name=\"text\">\n      <string>Certificate</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"5\" column=\"1\">\n    <widget class=\"QPushButton\" name=\"certificate\">\n     <property name=\"text\">\n      <string notr=\"true\">PushButton</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"6\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_5\">\n     <property name=\"text\">\n      <string>Insecure concurrency</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"6\" column=\"1\">\n    <widget class=\"MyLineEdit\" name=\"insecure_concurrency\"/>\n   </item>\n   <item row=\"7\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_8\">\n     <property name=\"text\">\n      <string>Disable logs</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"7\" column=\"1\">\n    <widget class=\"QCheckBox\" name=\"disable_log\">\n     <property name=\"text\">\n      <string>Turn on this option if your connection is lost after a while</string>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>MyLineEdit</class>\n   <extends>QLineEdit</extends>\n   <header>ui/widget/MyLineEdit.h</header>\n  </customwidget>\n </customwidgets>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "ui/edit/edit_quic.cpp",
    "content": "#include \"edit_quic.h\"\n#include \"ui_edit_quic.h\"\n\n#include \"fmt/QUICBean.hpp\"\n\n#include <QInputDialog>\n#include <QUuid>\n\nEditQUIC::EditQUIC(QWidget *parent) : QWidget(parent), ui(new Ui::EditQUIC) {\n    ui->setupUi(this);\n    connect(ui->uuidgen, &QPushButton::clicked, this, [=] { ui->uuid->setText(QUuid::createUuid().toString().remove(\"{\").remove(\"}\")); });\n}\n\nEditQUIC::~EditQUIC() {\n    delete ui;\n}\n\nvoid EditQUIC::onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) {\n    this->ent = _ent;\n    auto bean = this->ent->QUICBean();\n\n    P_LOAD_STRING(hopPort);\n    P_LOAD_INT(hopInterval);\n    P_LOAD_INT(uploadMbps);\n    P_LOAD_INT(downloadMbps);\n    P_LOAD_BOOL(disableMtuDiscovery)\n    P_LOAD_STRING(obfsPassword);\n    P_LOAD_INT(streamReceiveWindow);\n    P_LOAD_INT(connectionReceiveWindow);\n\n    P_LOAD_BOOL(forceExternal);\n    P_LOAD_STRING(uuid);\n    P_LOAD_STRING(password);\n\n    P_LOAD_COMBO_STRING(congestionControl);\n    P_LOAD_COMBO_STRING(udpRelayMode);\n    P_LOAD_BOOL(zeroRttHandshake);\n    P_LOAD_STRING(heartbeat);\n    P_LOAD_BOOL(uos);\n\n    // TLS\n    P_LOAD_STRING(sni);\n    P_LOAD_STRING(alpn);\n    P_C_LOAD_STRING(caText);\n    P_LOAD_BOOL(allowInsecure);\n    P_LOAD_BOOL(disableSni);\n\n    if (bean->proxy_type == NekoGui_fmt::QUICBean::proxy_Hysteria2) {\n        ui->uuid->hide();\n        ui->uuid_l->hide();\n        ui->uuidgen->hide();\n        ui->congestionControl->hide();\n        ui->congestionControl_l->hide();\n        ui->udpRelayMode->hide();\n        ui->udpRelayMode_l->hide();\n        ui->zeroRttHandshake->hide();\n        ui->heartbeat->hide();\n        ui->heartbeat_l->hide();\n        ui->uos->hide();\n\n        ui->alpn->hide();\n        ui->alpn_l->hide();\n        ui->TLS->removeItem(ui->alpn_sp);\n        ui->disableMtuDiscovery->hide();\n        ui->connectionReceiveWindow->hide();\n        ui->connectionReceiveWindow_l->hide();\n        ui->streamReceiveWindow->hide();\n        ui->streamReceiveWindow_l->hide();\n    } else if (bean->proxy_type == NekoGui_fmt::QUICBean::proxy_TUIC) {\n        ui->hopPort->hide();\n        ui->hopPort_l->hide();\n        ui->hopInterval->hide();\n        ui->hopInterval_l->hide();\n        ui->uploadMbps->hide();\n        ui->uploadMbps_l->hide();\n        ui->downloadMbps->hide();\n        ui->downloadMbps_l->hide();\n        ui->disableMtuDiscovery->hide();\n        ui->obfsPassword->hide();\n        ui->obfsPassword_l->hide();\n        ui->streamReceiveWindow->hide();\n        ui->streamReceiveWindow_l->hide();\n        ui->connectionReceiveWindow->hide();\n        ui->connectionReceiveWindow_l->hide();\n        ui->uos->hide();\n    }\n}\n\nbool EditQUIC::onEnd() {\n    auto bean = this->ent->QUICBean();\n\n    P_SAVE_BOOL(forceExternal);\n\n    // Hysteria 2\n    P_SAVE_STRING(hopPort);\n    P_SAVE_INT(hopInterval);\n    P_SAVE_INT(uploadMbps);\n    P_SAVE_INT(downloadMbps);\n    P_SAVE_BOOL(disableMtuDiscovery)\n    P_SAVE_STRING(obfsPassword);\n    P_SAVE_INT(streamReceiveWindow);\n    P_SAVE_INT(connectionReceiveWindow);\n\n    // TUIC\n    P_SAVE_STRING(uuid);\n    P_SAVE_STRING(password);\n    P_SAVE_COMBO_STRING(congestionControl);\n    P_SAVE_COMBO_STRING(udpRelayMode);\n    P_SAVE_BOOL(zeroRttHandshake);\n    P_SAVE_STRING(heartbeat);\n    P_SAVE_BOOL(uos);\n\n    // TLS\n    P_SAVE_STRING(sni);\n    P_SAVE_STRING(alpn);\n    P_SAVE_BOOL(allowInsecure);\n    P_C_SAVE_STRING(caText);\n    P_SAVE_BOOL(disableSni);\n    return true;\n}\n\nQList<QPair<QPushButton *, QString>> EditQUIC::get_editor_cached() {\n    return {\n        {ui->certificate, CACHE.caText},\n    };\n}\n\nvoid EditQUIC::on_certificate_clicked() {\n    bool ok;\n    auto txt = QInputDialog::getMultiLineText(this, tr(\"Certificate\"), \"\", CACHE.caText, &ok);\n    if (ok) {\n        CACHE.caText = txt;\n        editor_cache_updated();\n    }\n}\n"
  },
  {
    "path": "ui/edit/edit_quic.h",
    "content": "#pragma once\n\n#include <QWidget>\n#include <QGridLayout>\n#include <QHBoxLayout>\n#include \"profile_editor.h\"\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class EditQUIC;\n}\nQT_END_NAMESPACE\n\nclass EditQUIC : public QWidget, public ProfileEditor {\n    Q_OBJECT\n\npublic:\n    explicit EditQUIC(QWidget *parent = nullptr);\n\n    ~EditQUIC() override;\n\n    void onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) override;\n\n    bool onEnd() override;\n\n    QList<QPair<QPushButton *, QString>> get_editor_cached() override;\n\nprivate:\n    Ui::EditQUIC *ui;\n    std::shared_ptr<NekoGui::ProxyEntity> ent;\n\n    struct {\n        QString caText;\n    } CACHE;\n\nprivate slots:\n\n    void on_certificate_clicked();\n};\n"
  },
  {
    "path": "ui/edit/edit_quic.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>EditQUIC</class>\n <widget class=\"QWidget\" name=\"EditQUIC\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>500</width>\n    <height>628</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string notr=\"true\">EditHysteria</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <layout class=\"QGridLayout\" name=\"upBox\">\n     <item row=\"1\" column=\"1\">\n      <layout class=\"QHBoxLayout\" name=\"downloadMbpsLay\">\n       <item>\n        <widget class=\"QLabel\" name=\"downloadMbps_l\">\n         <property name=\"text\">\n          <string>Download (Mbps)</string>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"MyLineEdit\" name=\"downloadMbps\"/>\n       </item>\n      </layout>\n     </item>\n     <item row=\"0\" column=\"0\">\n      <layout class=\"QHBoxLayout\" name=\"hopPortLay\">\n       <item>\n        <widget class=\"QLabel\" name=\"hopPort_l\">\n         <property name=\"text\">\n          <string>Hop Port</string>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"MyLineEdit\" name=\"hopPort\"/>\n       </item>\n      </layout>\n     </item>\n     <item row=\"0\" column=\"1\">\n      <layout class=\"QHBoxLayout\" name=\"hopIntervalLay\">\n       <item>\n        <widget class=\"QLabel\" name=\"hopInterval_l\">\n         <property name=\"text\">\n          <string>Hop Interval (s)</string>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"MyLineEdit\" name=\"hopInterval\"/>\n       </item>\n      </layout>\n     </item>\n     <item row=\"3\" column=\"0\">\n      <layout class=\"QHBoxLayout\" name=\"heartbeatLay\">\n       <item>\n        <widget class=\"QLabel\" name=\"heartbeat_l\">\n         <property name=\"text\">\n          <string>Heartbeat</string>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"MyLineEdit\" name=\"heartbeat\">\n         <property name=\"sizePolicy\">\n          <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Fixed\">\n           <horstretch>0</horstretch>\n           <verstretch>0</verstretch>\n          </sizepolicy>\n         </property>\n        </widget>\n       </item>\n      </layout>\n     </item>\n     <item row=\"1\" column=\"0\">\n      <layout class=\"QHBoxLayout\" name=\"uploadMbpsLay\">\n       <item>\n        <widget class=\"QLabel\" name=\"uploadMbps_l\">\n         <property name=\"text\">\n          <string>Upload (Mbps)</string>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"MyLineEdit\" name=\"uploadMbps\"/>\n       </item>\n      </layout>\n     </item>\n     <item row=\"3\" column=\"1\">\n      <widget class=\"QCheckBox\" name=\"zeroRttHandshake\">\n       <property name=\"text\">\n        <string>Zero Rtt Handshake</string>\n       </property>\n      </widget>\n     </item>\n     <item row=\"2\" column=\"0\">\n      <layout class=\"QHBoxLayout\" name=\"congestionControlLay\">\n       <item>\n        <widget class=\"QLabel\" name=\"congestionControl_l\">\n         <property name=\"text\">\n          <string>Congestion Control</string>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"QComboBox\" name=\"congestionControl\">\n         <item>\n          <property name=\"text\">\n           <string notr=\"true\">bbr</string>\n          </property>\n         </item>\n         <item>\n          <property name=\"text\">\n           <string notr=\"true\">cubic</string>\n          </property>\n         </item>\n         <item>\n          <property name=\"text\">\n           <string notr=\"true\">new_reno</string>\n          </property>\n         </item>\n        </widget>\n       </item>\n      </layout>\n     </item>\n     <item row=\"2\" column=\"1\">\n      <layout class=\"QHBoxLayout\" name=\"udpRelayModeLay\">\n       <item>\n        <widget class=\"QLabel\" name=\"udpRelayMode_l\">\n         <property name=\"text\">\n          <string>UDP Relay Mode</string>\n         </property>\n        </widget>\n       </item>\n       <item>\n        <widget class=\"QComboBox\" name=\"udpRelayMode\">\n         <item>\n          <property name=\"text\">\n           <string notr=\"true\">native</string>\n          </property>\n         </item>\n         <item>\n          <property name=\"text\">\n           <string notr=\"true\">quic</string>\n          </property>\n         </item>\n        </widget>\n       </item>\n      </layout>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <item>\n      <widget class=\"QCheckBox\" name=\"forceExternal\">\n       <property name=\"text\">\n        <string>Force use external core</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QCheckBox\" name=\"uos\">\n       <property name=\"toolTip\">\n        <string notr=\"true\">Requires sing-box server</string>\n       </property>\n       <property name=\"text\">\n        <string notr=\"true\">UDP over Stream</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QCheckBox\" name=\"disableMtuDiscovery\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Fixed\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"text\">\n        <string>Disable MTU Discovery</string>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <layout class=\"QGridLayout\" name=\"obfuscation\">\n     <item row=\"0\" column=\"1\">\n      <widget class=\"MyLineEdit\" name=\"obfsPassword\"/>\n     </item>\n     <item row=\"0\" column=\"0\">\n      <widget class=\"QLabel\" name=\"obfsPassword_l\">\n       <property name=\"text\">\n        <string>Obfs Password</string>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <layout class=\"QGridLayout\" name=\"authentication\">\n     <item row=\"0\" column=\"2\">\n      <widget class=\"QPushButton\" name=\"uuidgen\">\n       <property name=\"text\">\n        <string>Generate UUID</string>\n       </property>\n      </widget>\n     </item>\n     <item row=\"0\" column=\"0\">\n      <widget class=\"QLabel\" name=\"uuid_l\">\n       <property name=\"text\">\n        <string notr=\"true\">UUID</string>\n       </property>\n      </widget>\n     </item>\n     <item row=\"0\" column=\"1\">\n      <widget class=\"MyLineEdit\" name=\"uuid\"/>\n     </item>\n     <item row=\"1\" column=\"0\">\n      <widget class=\"QLabel\" name=\"password_l\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"text\">\n        <string>Password</string>\n       </property>\n      </widget>\n     </item>\n     <item row=\"1\" column=\"1\" colspan=\"2\">\n      <widget class=\"MyLineEdit\" name=\"password\"/>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <layout class=\"QGridLayout\" name=\"TLS\" rowstretch=\"0,0,0\">\n     <item row=\"2\" column=\"0\">\n      <widget class=\"QLabel\" name=\"certificate_l\">\n       <property name=\"text\">\n        <string>Certificate</string>\n       </property>\n      </widget>\n     </item>\n     <item row=\"2\" column=\"1\">\n      <widget class=\"QPushButton\" name=\"certificate\">\n       <property name=\"text\">\n        <string notr=\"true\">PushButton</string>\n       </property>\n      </widget>\n     </item>\n     <item row=\"1\" column=\"1\">\n      <widget class=\"MyLineEdit\" name=\"alpn\"/>\n     </item>\n     <item row=\"0\" column=\"2\">\n      <widget class=\"QCheckBox\" name=\"disableSni\">\n       <property name=\"text\">\n        <string>Disable SNI</string>\n       </property>\n      </widget>\n     </item>\n     <item row=\"0\" column=\"1\">\n      <widget class=\"MyLineEdit\" name=\"sni\"/>\n     </item>\n     <item row=\"1\" column=\"0\">\n      <widget class=\"QLabel\" name=\"alpn_l\">\n       <property name=\"text\">\n        <string notr=\"true\">ALPN</string>\n       </property>\n      </widget>\n     </item>\n     <item row=\"0\" column=\"0\">\n      <widget class=\"QLabel\" name=\"sni_l\">\n       <property name=\"text\">\n        <string>SNI</string>\n       </property>\n      </widget>\n     </item>\n     <item row=\"2\" column=\"2\">\n      <widget class=\"QCheckBox\" name=\"allowInsecure\">\n       <property name=\"text\">\n        <string>Allow Insecure</string>\n       </property>\n      </widget>\n     </item>\n     <item row=\"1\" column=\"2\">\n      <spacer name=\"alpn_sp\">\n       <property name=\"orientation\">\n        <enum>Qt::Horizontal</enum>\n       </property>\n       <property name=\"sizeType\">\n        <enum>QSizePolicy::Maximum</enum>\n       </property>\n       <property name=\"sizeHint\" stdset=\"0\">\n        <size>\n         <width>40</width>\n         <height>20</height>\n        </size>\n       </property>\n      </spacer>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"flowControlWindow\">\n     <item>\n      <widget class=\"QLabel\" name=\"streamReceiveWindow_l\">\n       <property name=\"text\">\n        <string notr=\"true\">recv_window</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"MyLineEdit\" name=\"streamReceiveWindow\"/>\n     </item>\n     <item>\n      <widget class=\"QLabel\" name=\"connectionReceiveWindow_l\">\n       <property name=\"text\">\n        <string notr=\"true\">recv_window_conn</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"MyLineEdit\" name=\"connectionReceiveWindow\"/>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>MyLineEdit</class>\n   <extends>QLineEdit</extends>\n   <header>ui/widget/MyLineEdit.h</header>\n  </customwidget>\n </customwidgets>\n <tabstops>\n  <tabstop>hopPort</tabstop>\n  <tabstop>hopInterval</tabstop>\n  <tabstop>uploadMbps</tabstop>\n  <tabstop>downloadMbps</tabstop>\n  <tabstop>congestionControl</tabstop>\n  <tabstop>udpRelayMode</tabstop>\n  <tabstop>heartbeat</tabstop>\n  <tabstop>zeroRttHandshake</tabstop>\n  <tabstop>forceExternal</tabstop>\n  <tabstop>uos</tabstop>\n  <tabstop>disableMtuDiscovery</tabstop>\n  <tabstop>obfsPassword</tabstop>\n  <tabstop>uuid</tabstop>\n  <tabstop>uuidgen</tabstop>\n  <tabstop>password</tabstop>\n  <tabstop>sni</tabstop>\n  <tabstop>disableSni</tabstop>\n  <tabstop>alpn</tabstop>\n  <tabstop>certificate</tabstop>\n  <tabstop>allowInsecure</tabstop>\n  <tabstop>streamReceiveWindow</tabstop>\n  <tabstop>connectionReceiveWindow</tabstop>\n </tabstops>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "ui/edit/edit_shadowsocks.cpp",
    "content": "#include \"edit_shadowsocks.h\"\n#include \"ui_edit_shadowsocks.h\"\n\n#include \"fmt/ShadowSocksBean.hpp\"\n#include \"fmt/Preset.hpp\"\n\nEditShadowSocks::EditShadowSocks(QWidget *parent) : QWidget(parent),\n                                                    ui(new Ui::EditShadowSocks) {\n    ui->setupUi(this);\n    ui->method->addItems(Preset::SingBox::ShadowsocksMethods);\n}\n\nEditShadowSocks::~EditShadowSocks() {\n    delete ui;\n}\n\nvoid EditShadowSocks::onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) {\n    this->ent = _ent;\n    auto bean = this->ent->ShadowSocksBean();\n\n    ui->method->setCurrentText(bean->method);\n    ui->uot->setCurrentIndex(bean->uot);\n    ui->password->setText(bean->password);\n    auto ssPlugin = bean->plugin.split(\";\");\n    if (!ssPlugin.empty()) {\n        ui->plugin->setCurrentText(ssPlugin[0]);\n        ui->plugin_opts->setText(SubStrAfter(bean->plugin, \";\"));\n    }\n}\n\nbool EditShadowSocks::onEnd() {\n    auto bean = this->ent->ShadowSocksBean();\n\n    bean->method = ui->method->currentText();\n    bean->password = ui->password->text();\n    bean->uot = ui->uot->currentIndex();\n    bean->plugin = ui->plugin->currentText();\n    if (!bean->plugin.isEmpty()) {\n        bean->plugin += \";\" + ui->plugin_opts->text();\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "ui/edit/edit_shadowsocks.h",
    "content": "#ifndef EDIT_SHADOWSOCKS_H\n#define EDIT_SHADOWSOCKS_H\n\n#include <QWidget>\n#include \"profile_editor.h\"\n\nnamespace Ui {\n    class EditShadowSocks;\n}\n\nclass EditShadowSocks : public QWidget, public ProfileEditor {\n    Q_OBJECT\n\npublic:\n    explicit EditShadowSocks(QWidget *parent = nullptr);\n\n    ~EditShadowSocks() override;\n\n    void onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) override;\n\n    bool onEnd() override;\n\nprivate:\n    Ui::EditShadowSocks *ui;\n    std::shared_ptr<NekoGui::ProxyEntity> ent;\n};\n\n#endif // EDIT_SHADOWSOCKS_H\n"
  },
  {
    "path": "ui/edit/edit_shadowsocks.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>EditShadowSocks</class>\n <widget class=\"QWidget\" name=\"EditShadowSocks\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>400</width>\n    <height>300</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string notr=\"true\">Form</string>\n  </property>\n  <layout class=\"QGridLayout\" name=\"gridLayout\">\n   <item row=\"0\" column=\"1\">\n    <widget class=\"QComboBox\" name=\"method\">\n     <property name=\"editable\">\n      <bool>true</bool>\n     </property>\n    </widget>\n   </item>\n   <item row=\"1\" column=\"1\">\n    <widget class=\"MyLineEdit\" name=\"password\"/>\n   </item>\n   <item row=\"2\" column=\"0\">\n    <widget class=\"QLabel\" name=\"plugin_l\">\n     <property name=\"text\">\n      <string>Plugin</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"0\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label\">\n     <property name=\"text\">\n      <string>Encryption</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"3\" column=\"0\">\n    <widget class=\"QLabel\" name=\"plugin_opts_l\">\n     <property name=\"text\">\n      <string>Plugin Args</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"3\" column=\"1\">\n    <widget class=\"MyLineEdit\" name=\"plugin_opts\"/>\n   </item>\n   <item row=\"1\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_5\">\n     <property name=\"text\">\n      <string>Password</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"2\" column=\"1\">\n    <widget class=\"QComboBox\" name=\"plugin\">\n     <item>\n      <property name=\"text\">\n       <string/>\n      </property>\n     </item>\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\">obfs-local</string>\n      </property>\n     </item>\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\">v2ray-plugin</string>\n      </property>\n     </item>\n    </widget>\n   </item>\n   <item row=\"4\" column=\"0\">\n    <widget class=\"QLabel\" name=\"uot_l\">\n     <property name=\"toolTip\">\n      <string>Version of UDP over TCP protocol, server support is required.</string>\n     </property>\n     <property name=\"locale\">\n      <locale language=\"English\" country=\"UnitedStates\"/>\n     </property>\n     <property name=\"text\">\n      <string notr=\"true\">UoT</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"4\" column=\"1\">\n    <widget class=\"QComboBox\" name=\"uot\">\n     <item>\n      <property name=\"text\">\n       <string>Off</string>\n      </property>\n     </item>\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\">1</string>\n      </property>\n     </item>\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\">2</string>\n      </property>\n     </item>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>MyLineEdit</class>\n   <extends>QLineEdit</extends>\n   <header>ui/widget/MyLineEdit.h</header>\n  </customwidget>\n </customwidgets>\n <tabstops>\n  <tabstop>method</tabstop>\n  <tabstop>password</tabstop>\n  <tabstop>plugin</tabstop>\n  <tabstop>plugin_opts</tabstop>\n </tabstops>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "ui/edit/edit_socks_http.cpp",
    "content": "#include \"edit_socks_http.h\"\n#include \"ui_edit_socks_http.h\"\n\n#include \"fmt/SocksHttpBean.hpp\"\n\nEditSocksHttp::EditSocksHttp(QWidget *parent) : QWidget(parent),\n                                                ui(new Ui::EditSocksHttp) {\n    ui->setupUi(this);\n}\n\nEditSocksHttp::~EditSocksHttp() {\n    delete ui;\n}\n\nvoid EditSocksHttp::onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) {\n    this->ent = _ent;\n    auto bean = this->ent->SocksHTTPBean();\n\n    if (bean->socks_http_type == NekoGui_fmt::SocksHttpBean::type_Socks4) {\n        ui->version->setCurrentIndex(1);\n    } else {\n        ui->version->setCurrentIndex(0);\n    }\n    if (bean->socks_http_type == NekoGui_fmt::SocksHttpBean::type_HTTP) {\n        ui->version->setVisible(false);\n        ui->version_l->setVisible(false);\n    }\n\n    ui->username->setText(bean->username);\n    ui->password->setText(bean->password);\n}\n\nbool EditSocksHttp::onEnd() {\n    auto bean = this->ent->SocksHTTPBean();\n\n    if (ui->version->isVisible()) {\n        if (ui->version->currentIndex() == 1) {\n            bean->socks_http_type = NekoGui_fmt::SocksHttpBean::type_Socks4;\n        } else {\n            bean->socks_http_type = NekoGui_fmt::SocksHttpBean::type_Socks5;\n        }\n    }\n\n    bean->username = ui->username->text();\n    bean->password = ui->password->text();\n\n    return true;\n}\n"
  },
  {
    "path": "ui/edit/edit_socks_http.h",
    "content": "#pragma once\n\n#include <QWidget>\n#include \"profile_editor.h\"\n\nnamespace Ui {\n    class EditSocksHttp;\n}\n\nclass EditSocksHttp : public QWidget, public ProfileEditor {\n    Q_OBJECT\n\npublic:\n    explicit EditSocksHttp(QWidget *parent = nullptr);\n\n    ~EditSocksHttp() override;\n\n    void onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) override;\n\n    bool onEnd() override;\n\nprivate:\n    Ui::EditSocksHttp *ui;\n    std::shared_ptr<NekoGui::ProxyEntity> ent;\n};\n"
  },
  {
    "path": "ui/edit/edit_socks_http.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>EditSocksHttp</class>\n <widget class=\"QWidget\" name=\"EditSocksHttp\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>400</width>\n    <height>300</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string notr=\"true\">Form</string>\n  </property>\n  <layout class=\"QGridLayout\" name=\"gridLayout\">\n   <item row=\"0\" column=\"0\">\n    <widget class=\"QLabel\" name=\"version_l\">\n     <property name=\"text\">\n      <string>Version</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"1\" column=\"1\">\n    <widget class=\"QLineEdit\" name=\"username\"/>\n   </item>\n   <item row=\"2\" column=\"1\">\n    <widget class=\"QLineEdit\" name=\"password\"/>\n   </item>\n   <item row=\"1\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_4\">\n     <property name=\"text\">\n      <string>Username</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"0\" column=\"1\">\n    <widget class=\"QComboBox\" name=\"version\">\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\">5</string>\n      </property>\n     </item>\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\">4</string>\n      </property>\n     </item>\n    </widget>\n   </item>\n   <item row=\"2\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_5\">\n     <property name=\"text\">\n      <string>Password</string>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <tabstops>\n  <tabstop>version</tabstop>\n  <tabstop>username</tabstop>\n  <tabstop>password</tabstop>\n </tabstops>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "ui/edit/edit_trojan_vless.cpp",
    "content": "#include \"edit_trojan_vless.h\"\n#include \"ui_edit_trojan_vless.h\"\n\n#include \"fmt/TrojanVLESSBean.hpp\"\n#include \"fmt/Preset.hpp\"\n\nEditTrojanVLESS::EditTrojanVLESS(QWidget *parent) : QWidget(parent), ui(new Ui::EditTrojanVLESS) {\n    ui->setupUi(this);\n}\n\nEditTrojanVLESS::~EditTrojanVLESS() {\n    delete ui;\n}\n\nvoid EditTrojanVLESS::onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) {\n    this->ent = _ent;\n    auto bean = this->ent->TrojanVLESSBean();\n    if (bean->proxy_type == NekoGui_fmt::TrojanVLESSBean::proxy_VLESS) {\n        ui->label->setText(\"UUID\");\n    }\n    if (bean->proxy_type != NekoGui_fmt::TrojanVLESSBean::proxy_VLESS) {\n        ui->flow->hide();\n        ui->flow_l->hide();\n    }\n    ui->password->setText(bean->password);\n    ui->flow->addItems(Preset::SingBox::Flows);\n    ui->flow->setCurrentText(bean->flow);\n}\n\nbool EditTrojanVLESS::onEnd() {\n    auto bean = this->ent->TrojanVLESSBean();\n    bean->password = ui->password->text();\n    bean->flow = ui->flow->currentText();\n    return true;\n}\n"
  },
  {
    "path": "ui/edit/edit_trojan_vless.h",
    "content": "#pragma once\n\n#include <QWidget>\n#include \"profile_editor.h\"\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class EditTrojanVLESS;\n}\nQT_END_NAMESPACE\n\nclass EditTrojanVLESS : public QWidget, public ProfileEditor {\n    Q_OBJECT\n\npublic:\n    explicit EditTrojanVLESS(QWidget *parent = nullptr);\n\n    ~EditTrojanVLESS() override;\n\n    void onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) override;\n\n    bool onEnd() override;\n\nprivate:\n    Ui::EditTrojanVLESS *ui;\n    std::shared_ptr<NekoGui::ProxyEntity> ent;\n};\n"
  },
  {
    "path": "ui/edit/edit_trojan_vless.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>EditTrojanVLESS</class>\n <widget class=\"QWidget\" name=\"EditTrojanVLESS\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>400</width>\n    <height>300</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string notr=\"true\"/>\n  </property>\n  <layout class=\"QGridLayout\" name=\"gridLayout\">\n   <item row=\"0\" column=\"1\">\n    <widget class=\"MyLineEdit\" name=\"password\"/>\n   </item>\n   <item row=\"0\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label\">\n     <property name=\"text\">\n      <string>Password</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"1\" column=\"0\">\n    <widget class=\"QLabel\" name=\"flow_l\">\n     <property name=\"text\">\n      <string notr=\"true\">Flow</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"1\" column=\"1\">\n    <widget class=\"QComboBox\" name=\"flow\">\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\"/>\n      </property>\n     </item>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>MyLineEdit</class>\n   <extends>QLineEdit</extends>\n   <header>ui/widget/MyLineEdit.h</header>\n  </customwidget>\n </customwidgets>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "ui/edit/edit_vmess.cpp",
    "content": "#include \"edit_vmess.h\"\n#include \"ui_edit_vmess.h\"\n\n#include \"fmt/VMessBean.hpp\"\n\n#include <QUuid>\n\nEditVMess::EditVMess(QWidget *parent) : QWidget(parent), ui(new Ui::EditVMess) {\n    ui->setupUi(this);\n    connect(ui->uuidgen, &QPushButton::clicked, this, [=] { ui->uuid->setText(QUuid::createUuid().toString().remove(\"{\").remove(\"}\")); });\n}\n\nEditVMess::~EditVMess() {\n    delete ui;\n}\n\nvoid EditVMess::onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) {\n    this->ent = _ent;\n    auto bean = this->ent->VMessBean();\n\n    ui->uuid->setText(bean->uuid);\n    ui->aid->setText(Int2String(bean->aid));\n    ui->security->setCurrentText(bean->security);\n}\n\nbool EditVMess::onEnd() {\n    auto bean = this->ent->VMessBean();\n\n    bean->uuid = ui->uuid->text();\n    bean->aid = ui->aid->text().toInt();\n    bean->security = ui->security->currentText();\n\n    return true;\n}\n"
  },
  {
    "path": "ui/edit/edit_vmess.h",
    "content": "#pragma once\n\n#include <QWidget>\n#include \"profile_editor.h\"\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class EditVMess;\n}\nQT_END_NAMESPACE\n\nclass EditVMess : public QWidget, public ProfileEditor {\n    Q_OBJECT\n\npublic:\n    explicit EditVMess(QWidget *parent = nullptr);\n\n    ~EditVMess() override;\n\n    void onStart(std::shared_ptr<NekoGui::ProxyEntity> _ent) override;\n\n    bool onEnd() override;\n\nprivate:\n    Ui::EditVMess *ui;\n    std::shared_ptr<NekoGui::ProxyEntity> ent;\n};\n"
  },
  {
    "path": "ui/edit/edit_vmess.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>EditVMess</class>\n <widget class=\"QWidget\" name=\"EditVMess\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>400</width>\n    <height>300</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string notr=\"true\">EditVMess</string>\n  </property>\n  <layout class=\"QGridLayout\" name=\"gridLayout\">\n   <item row=\"4\" column=\"1\">\n    <widget class=\"QComboBox\" name=\"security\">\n     <property name=\"editable\">\n      <bool>true</bool>\n     </property>\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\">auto</string>\n      </property>\n     </item>\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\">zero</string>\n      </property>\n     </item>\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\">none</string>\n      </property>\n     </item>\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\">chacha20-poly1305</string>\n      </property>\n     </item>\n     <item>\n      <property name=\"text\">\n       <string notr=\"true\">aes-128-gcm</string>\n      </property>\n     </item>\n    </widget>\n   </item>\n   <item row=\"4\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_3\">\n     <property name=\"text\">\n      <string>Security</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"3\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label_2\">\n     <property name=\"text\">\n      <string>Alter Id</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"0\" column=\"0\">\n    <widget class=\"QLabel\" name=\"label\">\n     <property name=\"text\">\n      <string>UUID</string>\n     </property>\n    </widget>\n   </item>\n   <item row=\"0\" column=\"1\">\n    <widget class=\"MyLineEdit\" name=\"uuid\"/>\n   </item>\n   <item row=\"3\" column=\"1\">\n    <widget class=\"QWidget\" name=\"horizontalWidget\" native=\"true\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n      <property name=\"leftMargin\">\n       <number>0</number>\n      </property>\n      <property name=\"topMargin\">\n       <number>0</number>\n      </property>\n      <property name=\"rightMargin\">\n       <number>0</number>\n      </property>\n      <property name=\"bottomMargin\">\n       <number>0</number>\n      </property>\n      <item>\n       <widget class=\"QLineEdit\" name=\"aid\"/>\n      </item>\n      <item>\n       <widget class=\"QPushButton\" name=\"uuidgen\">\n        <property name=\"text\">\n         <string>Generate UUID</string>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>MyLineEdit</class>\n   <extends>QLineEdit</extends>\n   <header>ui/widget/MyLineEdit.h</header>\n  </customwidget>\n </customwidgets>\n <tabstops>\n  <tabstop>uuid</tabstop>\n  <tabstop>aid</tabstop>\n  <tabstop>uuidgen</tabstop>\n  <tabstop>security</tabstop>\n </tabstops>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "ui/edit/profile_editor.h",
    "content": "#pragma once\n\n#include <QPushButton>\n\n#include \"db/ProxyEntity.hpp\"\n#include \"main/GuiUtils.hpp\"\n\nclass ProfileEditor {\npublic:\n    virtual void onStart(std::shared_ptr<NekoGui::ProxyEntity> ent) = 0;\n\n    virtual bool onEnd() = 0;\n\n    std::function<QWidget *()> get_edit_dialog;\n    std::function<QString()> get_edit_text_name;\n    std::function<QString()> get_edit_text_serverAddress;\n    std::function<QString()> get_edit_text_serverPort;\n\n    // cached editor\n\n    std::function<void()> editor_cache_updated;\n\n    virtual QList<QPair<QPushButton *, QString>> get_editor_cached() { return {}; };\n};\n"
  },
  {
    "path": "ui/mainwindow.cpp",
    "content": "#include \"./ui_mainwindow.h\"\n#include \"mainwindow.h\"\n\n#include \"fmt/Preset.hpp\"\n#include \"db/ProfileFilter.hpp\"\n#include \"db/ConfigBuilder.hpp\"\n#include \"sub/GroupUpdater.hpp\"\n#include \"sys/ExternalProcess.hpp\"\n#include \"sys/AutoRun.hpp\"\n\n#include \"ui/ThemeManager.hpp\"\n#include \"ui/Icon.hpp\"\n#include \"ui/edit/dialog_edit_profile.h\"\n#include \"ui/dialog_basic_settings.h\"\n#include \"ui/dialog_manage_groups.h\"\n#include \"ui/dialog_manage_routes.h\"\n#include \"ui/dialog_vpn_settings.h\"\n#include \"ui/dialog_hotkey.h\"\n\n#include \"3rdparty/fix_old_qt.h\"\n#include \"3rdparty/qrcodegen.hpp\"\n#include \"3rdparty/VT100Parser.hpp\"\n#include \"3rdparty/qv2ray/v2/components/proxy/QvProxyConfigurator.hpp\"\n\n#ifndef NKR_NO_ZXING\n#include \"3rdparty/ZxingQtReader.hpp\"\n#endif\n\n#ifdef Q_OS_WIN\n#include \"3rdparty/WinCommander.hpp\"\n#else\n#ifdef Q_OS_LINUX\n#include \"sys/linux/LinuxCap.h\"\n#endif\n#include <unistd.h>\n#endif\n\n#include <QClipboard>\n#include <QLabel>\n#include <QTextBlock>\n#include <QScrollBar>\n#include <QScreen>\n#include <QDesktopServices>\n#include <QInputDialog>\n#include <QThread>\n#include <QTimer>\n#include <QMessageBox>\n#include <QDir>\n#include <QFileInfo>\n\nvoid UI_InitMainWindow() {\n    mainwindow = new MainWindow;\n}\n\nMainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {\n    mainwindow = this;\n    MW_dialog_message = [=](const QString &a, const QString &b) {\n        runOnUiThread([=] { dialog_message_impl(a, b); });\n    };\n\n    // Load Manager\n    NekoGui::profileManager->LoadManager();\n\n    // Setup misc UI\n    themeManager->ApplyTheme(NekoGui::dataStore->theme);\n    ui->setupUi(this);\n    //\n    connect(ui->menu_start, &QAction::triggered, this, [=]() { neko_start(); });\n    connect(ui->menu_stop, &QAction::triggered, this, [=]() { neko_stop(); });\n    connect(ui->tabWidget->tabBar(), &QTabBar::tabMoved, this, [=](int from, int to) {\n        // use tabData to track tab & gid\n        NekoGui::profileManager->groupsTabOrder.clear();\n        for (int i = 0; i < ui->tabWidget->tabBar()->count(); i++) {\n            NekoGui::profileManager->groupsTabOrder += ui->tabWidget->tabBar()->tabData(i).toInt();\n        }\n        NekoGui::profileManager->SaveManager();\n    });\n    ui->label_running->installEventFilter(this);\n    ui->label_inbound->installEventFilter(this);\n    ui->splitter->installEventFilter(this);\n    //\n    RegisterHotkey(false);\n    //\n    auto last_size = NekoGui::dataStore->mw_size.split(\"x\");\n    if (last_size.length() == 2) {\n        auto w = last_size[0].toInt();\n        auto h = last_size[1].toInt();\n        if (w > 0 && h > 0) {\n            resize(w, h);\n        }\n    }\n\n    if (QDir(\"dashboard\").count() == 0) {\n        QDir().mkdir(\"dashboard\");\n        QFile::copy(\":/neko/dashboard-notice.html\", \"dashboard/index.html\");\n    }\n\n    // top bar\n    ui->toolButton_program->setMenu(ui->menu_program);\n    ui->toolButton_preferences->setMenu(ui->menu_preferences);\n    ui->toolButton_server->setMenu(ui->menu_server);\n    ui->menubar->setVisible(false);\n    connect(ui->toolButton_document, &QToolButton::clicked, this, [=] { QDesktopServices::openUrl(QUrl(\"https://matsuridayo.github.io/\")); });\n    connect(ui->toolButton_ads, &QToolButton::clicked, this, [=] { QDesktopServices::openUrl(QUrl(\"https://neko-box.pages.dev/喵\")); });\n    connect(ui->toolButton_update, &QToolButton::clicked, this, [=] { runOnNewThread([=] { CheckUpdate(); }); });\n    connect(ui->toolButton_url_test, &QToolButton::clicked, this, [=] { speedtest_current_group(1, true); });\n\n    // Setup log UI\n    ui->splitter->restoreState(DecodeB64IfValid(NekoGui::dataStore->splitter_state));\n    qvLogDocument->setUndoRedoEnabled(false);\n    ui->masterLogBrowser->setUndoRedoEnabled(false);\n    ui->masterLogBrowser->setDocument(qvLogDocument);\n    ui->masterLogBrowser->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));\n    {\n        auto font = ui->masterLogBrowser->font();\n        font.setPointSize(9);\n        ui->masterLogBrowser->setFont(font);\n        qvLogDocument->setDefaultFont(font);\n    }\n    connect(ui->masterLogBrowser->verticalScrollBar(), &QSlider::valueChanged, this, [=](int value) {\n        if (ui->masterLogBrowser->verticalScrollBar()->maximum() == value)\n            qvLogAutoScoll = true;\n        else\n            qvLogAutoScoll = false;\n    });\n    connect(ui->masterLogBrowser, &QTextBrowser::textChanged, this, [=]() {\n        if (!qvLogAutoScoll)\n            return;\n        auto bar = ui->masterLogBrowser->verticalScrollBar();\n        bar->setValue(bar->maximum());\n    });\n    MW_show_log = [=](const QString &log) {\n        runOnUiThread([=] { show_log_impl(log); });\n    };\n    MW_show_log_ext = [=](const QString &tag, const QString &log) {\n        runOnUiThread([=] { show_log_impl(\"[\" + tag + \"] \" + log); });\n    };\n    MW_show_log_ext_vt100 = [=](const QString &log) {\n        runOnUiThread([=] { show_log_impl(cleanVT100String(log)); });\n    };\n\n    // table UI\n    ui->proxyListTable->callback_save_order = [=] {\n        auto group = NekoGui::profileManager->CurrentGroup();\n        group->order = ui->proxyListTable->order;\n        group->Save();\n    };\n    ui->proxyListTable->refresh_data = [=](int id) { refresh_proxy_list_impl_refresh_data(id); };\n    if (auto button = ui->proxyListTable->findChild<QAbstractButton *>(QString(), Qt::FindDirectChildrenOnly)) {\n        // Corner Button\n        connect(button, &QAbstractButton::clicked, this, [=] { refresh_proxy_list_impl(-1, {GroupSortMethod::ById}); });\n    }\n    connect(ui->proxyListTable->horizontalHeader(), &QHeaderView::sectionClicked, this, [=](int logicalIndex) {\n        GroupSortAction action;\n        // 不正确的descending实现\n        if (proxy_last_order == logicalIndex) {\n            action.descending = true;\n            proxy_last_order = -1;\n        } else {\n            proxy_last_order = logicalIndex;\n        }\n        action.save_sort = true;\n        // 表头\n        if (logicalIndex == 0) {\n            action.method = GroupSortMethod::ByType;\n        } else if (logicalIndex == 1) {\n            action.method = GroupSortMethod::ByAddress;\n        } else if (logicalIndex == 2) {\n            action.method = GroupSortMethod::ByName;\n        } else if (logicalIndex == 3) {\n            action.method = GroupSortMethod::ByLatency;\n        } else {\n            return;\n        }\n        refresh_proxy_list_impl(-1, action);\n    });\n    connect(ui->proxyListTable->horizontalHeader(), &QHeaderView::sectionResized, this, [=](int logicalIndex, int oldSize, int newSize) {\n        auto group = NekoGui::profileManager->CurrentGroup();\n        if (NekoGui::dataStore->refreshing_group || group == nullptr || !group->manually_column_width) return;\n        // save manually column width\n        group->column_width.clear();\n        for (int i = 0; i < ui->proxyListTable->horizontalHeader()->count(); i++) {\n            group->column_width.push_back(ui->proxyListTable->horizontalHeader()->sectionSize(i));\n        }\n        group->column_width[logicalIndex] = newSize;\n        group->Save();\n    });\n    ui->tableWidget_conn->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);\n    ui->tableWidget_conn->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);\n    ui->tableWidget_conn->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);\n    ui->proxyListTable->verticalHeader()->setDefaultSectionSize(24);\n\n    // search box\n    ui->search->setVisible(false);\n    connect(shortcut_ctrl_f, &QShortcut::activated, this, [=] {\n        ui->search->setVisible(true);\n        ui->search->setFocus();\n    });\n    connect(shortcut_esc, &QShortcut::activated, this, [=] {\n        if (ui->search->isVisible()) {\n            ui->search->setText(\"\");\n            ui->search->textChanged(\"\");\n            ui->search->setVisible(false);\n        }\n        if (select_mode) {\n            emit profile_selected(-1);\n            select_mode = false;\n            refresh_status();\n        }\n    });\n    connect(ui->search, &QLineEdit::textChanged, this, [=](const QString &text) {\n        if (text.isEmpty()) {\n            for (int i = 0; i < ui->proxyListTable->rowCount(); i++) {\n                ui->proxyListTable->setRowHidden(i, false);\n            }\n        } else {\n            QList<QTableWidgetItem *> findItem = ui->proxyListTable->findItems(text, Qt::MatchContains);\n            for (int i = 0; i < ui->proxyListTable->rowCount(); i++) {\n                ui->proxyListTable->setRowHidden(i, true);\n            }\n            for (auto item: findItem) {\n                if (item != nullptr) ui->proxyListTable->setRowHidden(item->row(), false);\n            }\n        }\n    });\n\n    // refresh\n    this->refresh_groups();\n\n    // Setup Tray\n    tray = new QSystemTrayIcon(this); // 初始化托盘对象tray\n    tray->setIcon(Icon::GetTrayIcon(Icon::NONE));\n    tray->setContextMenu(ui->menu_program); // 创建托盘菜单\n    tray->show();                           // 让托盘图标显示在系统托盘上\n    connect(tray, &QSystemTrayIcon::activated, this, [=](QSystemTrayIcon::ActivationReason reason) {\n        if (reason == QSystemTrayIcon::Trigger) {\n            if (this->isVisible()) {\n                hide();\n            } else {\n                ActivateWindow(this);\n            }\n        }\n    });\n\n    // Misc menu\n    connect(ui->menu_open_config_folder, &QAction::triggered, this, [=] { QDesktopServices::openUrl(QUrl::fromLocalFile(QDir::currentPath())); });\n    ui->menu_program_preference->addActions(ui->menu_preferences->actions());\n    connect(ui->menu_add_from_clipboard2, &QAction::triggered, ui->menu_add_from_clipboard, &QAction::trigger);\n    connect(ui->actionRestart_Proxy, &QAction::triggered, this, [=] { if (NekoGui::dataStore->started_id>=0) neko_start(NekoGui::dataStore->started_id); });\n    connect(ui->actionRestart_Program, &QAction::triggered, this, [=] { MW_dialog_message(\"\", \"RestartProgram\"); });\n    connect(ui->actionShow_window, &QAction::triggered, this, [=] { tray->activated(QSystemTrayIcon::ActivationReason::Trigger); });\n    //\n    connect(ui->menu_program, &QMenu::aboutToShow, this, [=]() {\n        ui->actionRemember_last_proxy->setChecked(NekoGui::dataStore->remember_enable);\n        ui->actionStart_with_system->setChecked(AutoRun_IsEnabled());\n        ui->actionAllow_LAN->setChecked(QStringList{\"::\", \"0.0.0.0\"}.contains(NekoGui::dataStore->inbound_address));\n        // active server\n        for (const auto &old: ui->menuActive_Server->actions()) {\n            ui->menuActive_Server->removeAction(old);\n            old->deleteLater();\n        }\n        int active_server_item_count = 0;\n        for (const auto &pf: NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder()) {\n            auto a = new QAction(pf->bean->DisplayTypeAndName(), this);\n            a->setProperty(\"id\", pf->id);\n            a->setCheckable(true);\n            if (NekoGui::dataStore->started_id == pf->id) a->setChecked(true);\n            ui->menuActive_Server->addAction(a);\n            if (++active_server_item_count == 100) break;\n        }\n        // active routing\n        for (const auto &old: ui->menuActive_Routing->actions()) {\n            ui->menuActive_Routing->removeAction(old);\n            old->deleteLater();\n        }\n        for (const auto &name: NekoGui::Routing::List()) {\n            auto a = new QAction(name, this);\n            a->setCheckable(true);\n            a->setChecked(name == NekoGui::dataStore->active_routing);\n            ui->menuActive_Routing->addAction(a);\n        }\n    });\n    connect(ui->menuActive_Server, &QMenu::triggered, this, [=](QAction *a) {\n        bool ok;\n        auto id = a->property(\"id\").toInt(&ok);\n        if (!ok) return;\n        if (NekoGui::dataStore->started_id == id) {\n            neko_stop();\n        } else {\n            neko_start(id);\n        }\n    });\n    connect(ui->menuActive_Routing, &QMenu::triggered, this, [=](QAction *a) {\n        auto fn = a->text();\n        if (!fn.isEmpty()) {\n            NekoGui::Routing r;\n            r.load_control_must = true;\n            r.fn = ROUTES_PREFIX + fn;\n            if (r.Load()) {\n                if (QMessageBox::question(GetMessageBoxParent(), software_name, tr(\"Load routing and apply: %1\").arg(fn) + \"\\n\" + r.DisplayRouting()) == QMessageBox::Yes) {\n                    NekoGui::Routing::SetToActive(fn);\n                    if (NekoGui::dataStore->started_id >= 0) {\n                        neko_start(NekoGui::dataStore->started_id);\n                    } else {\n                        refresh_status();\n                    }\n                }\n            }\n        }\n    });\n    connect(ui->actionRemember_last_proxy, &QAction::triggered, this, [=](bool checked) {\n        NekoGui::dataStore->remember_enable = checked;\n        NekoGui::dataStore->Save();\n    });\n    connect(ui->actionStart_with_system, &QAction::triggered, this, [=](bool checked) {\n        AutoRun_SetEnabled(checked);\n    });\n    connect(ui->actionAllow_LAN, &QAction::triggered, this, [=](bool checked) {\n        NekoGui::dataStore->inbound_address = checked ? \"::\" : \"127.0.0.1\";\n        MW_dialog_message(\"\", \"UpdateDataStore\");\n    });\n    //\n    connect(ui->checkBox_VPN, &QCheckBox::clicked, this, [=](bool checked) { neko_set_spmode_vpn(checked); });\n    connect(ui->checkBox_SystemProxy, &QCheckBox::clicked, this, [=](bool checked) { neko_set_spmode_system_proxy(checked); });\n    connect(ui->menu_spmode, &QMenu::aboutToShow, this, [=]() {\n        ui->menu_spmode_disabled->setChecked(!(NekoGui::dataStore->spmode_system_proxy || NekoGui::dataStore->spmode_vpn));\n        ui->menu_spmode_system_proxy->setChecked(NekoGui::dataStore->spmode_system_proxy);\n        ui->menu_spmode_vpn->setChecked(NekoGui::dataStore->spmode_vpn);\n    });\n    connect(ui->menu_spmode_system_proxy, &QAction::triggered, this, [=](bool checked) { neko_set_spmode_system_proxy(checked); });\n    connect(ui->menu_spmode_vpn, &QAction::triggered, this, [=](bool checked) { neko_set_spmode_vpn(checked); });\n    connect(ui->menu_spmode_disabled, &QAction::triggered, this, [=]() {\n        neko_set_spmode_system_proxy(false);\n        neko_set_spmode_vpn(false);\n    });\n    connect(ui->menu_qr, &QAction::triggered, this, [=]() { display_qr_link(false); });\n    connect(ui->menu_tcp_ping, &QAction::triggered, this, [=]() { speedtest_current_group(0, false); });\n    connect(ui->menu_url_test, &QAction::triggered, this, [=]() { speedtest_current_group(1, false); });\n    connect(ui->menu_full_test, &QAction::triggered, this, [=]() { speedtest_current_group(2, false); });\n    connect(ui->menu_stop_testing, &QAction::triggered, this, [=]() { speedtest_current_group(114514, false); });\n    //\n    auto set_selected_or_group = [=](int mode) {\n        // 0=group 1=select 2=unknown(menu is hide)\n        ui->menu_server->setProperty(\"selected_or_group\", mode);\n    };\n    auto move_tests_to_menu = [=](bool menuCurrent_Select) {\n        return [=] {\n            if (menuCurrent_Select) {\n                ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_tcp_ping);\n                ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_url_test);\n                ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_full_test);\n                ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_stop_testing);\n                ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_clear_test_result);\n                ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_resolve_domain);\n            } else {\n                ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_tcp_ping);\n                ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_url_test);\n                ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_full_test);\n                ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_stop_testing);\n                ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_clear_test_result);\n                ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_resolve_domain);\n            }\n            set_selected_or_group(menuCurrent_Select ? 1 : 0);\n        };\n    };\n    connect(ui->menuCurrent_Select, &QMenu::aboutToShow, this, move_tests_to_menu(true));\n    connect(ui->menuCurrent_Group, &QMenu::aboutToShow, this, move_tests_to_menu(false));\n    connect(ui->menu_server, &QMenu::aboutToHide, this, [=] {\n        setTimeout([=] { set_selected_or_group(2); }, this, 200);\n    });\n    set_selected_or_group(2);\n    //\n    connect(ui->menu_share_item, &QMenu::aboutToShow, this, [=] {\n        QString name;\n        auto selected = get_now_selected_list();\n        if (!selected.isEmpty()) {\n            auto ent = selected.first();\n            name = ent->bean->DisplayCoreType();\n        }\n        ui->menu_export_config->setVisible(name == software_core_name);\n        ui->menu_export_config->setText(tr(\"Export %1 config\").arg(name));\n    });\n    refresh_status();\n\n    // Prepare core\n    NekoGui::dataStore->core_token = GetRandomString(32);\n    NekoGui::dataStore->core_port = MkPort();\n    if (NekoGui::dataStore->core_port <= 0) NekoGui::dataStore->core_port = 19810;\n\n    auto core_path = QApplication::applicationDirPath() + \"/\";\n    core_path += \"nekobox_core\";\n\n    QStringList args;\n    args.push_back(\"nekobox\");\n    args.push_back(\"-port\");\n    args.push_back(Int2String(NekoGui::dataStore->core_port));\n    if (NekoGui::dataStore->flag_debug) args.push_back(\"-debug\");\n\n    // Start core\n    runOnUiThread(\n        [=] {\n            core_process = new NekoGui_sys::CoreProcess(core_path, args);\n            // Remember last started\n            if (NekoGui::dataStore->remember_enable && NekoGui::dataStore->remember_id >= 0) {\n                core_process->start_profile_when_core_is_up = NekoGui::dataStore->remember_id;\n            }\n            // Setup\n            core_process->Start();\n            setup_grpc();\n        },\n        DS_cores);\n\n    // Remember system proxy\n    if (NekoGui::dataStore->remember_enable || NekoGui::dataStore->flag_restart_tun_on) {\n        if (NekoGui::dataStore->remember_spmode.contains(\"system_proxy\")) {\n            neko_set_spmode_system_proxy(true, false);\n        }\n        if (NekoGui::dataStore->remember_spmode.contains(\"vpn\") || NekoGui::dataStore->flag_restart_tun_on) {\n            neko_set_spmode_vpn(true, false);\n        }\n    }\n\n    connect(qApp, &QGuiApplication::commitDataRequest, this, &MainWindow::on_commitDataRequest);\n\n    auto t = new QTimer;\n    connect(t, &QTimer::timeout, this, [=]() { refresh_status(); });\n    t->start(2000);\n\n    t = new QTimer;\n    connect(t, &QTimer::timeout, this, [&] { NekoGui_sys::logCounter.fetchAndStoreRelaxed(0); });\n    t->start(1000);\n\n    TM_auto_update_subsctiption = new QTimer;\n    TM_auto_update_subsctiption_Reset_Minute = [&](int m) {\n        TM_auto_update_subsctiption->stop();\n        if (m >= 30) TM_auto_update_subsctiption->start(m * 60 * 1000);\n    };\n    connect(TM_auto_update_subsctiption, &QTimer::timeout, this, [&] { UI_update_all_groups(true); });\n    TM_auto_update_subsctiption_Reset_Minute(NekoGui::dataStore->sub_auto_update);\n\n    if (!NekoGui::dataStore->flag_tray) show();\n}\n\nvoid MainWindow::closeEvent(QCloseEvent *event) {\n    if (tray->isVisible()) {\n        hide();          // 隐藏窗口\n        event->ignore(); // 忽略事件\n    }\n}\n\nMainWindow::~MainWindow() {\n    delete ui;\n}\n\n// Group tab manage\n\ninline int tabIndex2GroupId(int index) {\n    if (NekoGui::profileManager->groupsTabOrder.length() <= index) return -1;\n    return NekoGui::profileManager->groupsTabOrder[index];\n}\n\ninline int groupId2TabIndex(int gid) {\n    for (int key = 0; key < NekoGui::profileManager->groupsTabOrder.count(); key++) {\n        if (NekoGui::profileManager->groupsTabOrder[key] == gid) return key;\n    }\n    return 0;\n}\n\nvoid MainWindow::on_tabWidget_currentChanged(int index) {\n    if (NekoGui::dataStore->refreshing_group_list) return;\n    if (tabIndex2GroupId(index) == NekoGui::dataStore->current_group) return;\n    show_group(tabIndex2GroupId(index));\n}\n\nvoid MainWindow::show_group(int gid) {\n    if (NekoGui::dataStore->refreshing_group) return;\n    NekoGui::dataStore->refreshing_group = true;\n\n    auto group = NekoGui::profileManager->GetGroup(gid);\n    if (group == nullptr) {\n        MessageBoxWarning(tr(\"Error\"), QStringLiteral(\"No such group: %1\").arg(gid));\n        NekoGui::dataStore->refreshing_group = false;\n        return;\n    }\n\n    if (NekoGui::dataStore->current_group != gid) {\n        NekoGui::dataStore->current_group = gid;\n        NekoGui::dataStore->Save();\n    }\n    ui->tabWidget->widget(groupId2TabIndex(gid))->layout()->addWidget(ui->proxyListTable);\n\n    // 列宽是否可调\n    if (group->manually_column_width) {\n        for (int i = 0; i <= 4; i++) {\n            ui->proxyListTable->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Interactive);\n            auto size = group->column_width.value(i);\n            if (size <= 0) size = ui->proxyListTable->horizontalHeader()->defaultSectionSize();\n            ui->proxyListTable->horizontalHeader()->resizeSection(i, size);\n        }\n    } else {\n        ui->proxyListTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);\n        ui->proxyListTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);\n        ui->proxyListTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);\n        ui->proxyListTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents);\n        ui->proxyListTable->horizontalHeader()->setSectionResizeMode(4, QHeaderView::ResizeToContents);\n    }\n\n    // show proxies\n    GroupSortAction gsa;\n    gsa.scroll_to_started = true;\n    refresh_proxy_list_impl(-1, gsa);\n\n    NekoGui::dataStore->refreshing_group = false;\n}\n\n// callback\n\nvoid MainWindow::dialog_message_impl(const QString &sender, const QString &info) {\n    // info\n    if (info.contains(\"UpdateIcon\")) {\n        icon_status = -1;\n        refresh_status();\n    }\n    if (info.contains(\"UpdateDataStore\")) {\n        auto suggestRestartProxy = NekoGui::dataStore->Save();\n        if (info.contains(\"RouteChanged\")) {\n            suggestRestartProxy = true;\n        }\n        if (info.contains(\"NeedRestart\")) {\n            suggestRestartProxy = false;\n        }\n        refresh_proxy_list();\n        if (info.contains(\"VPNChanged\") && NekoGui::dataStore->spmode_vpn) {\n            MessageBoxWarning(tr(\"Tun Settings changed\"), tr(\"Restart Tun to take effect.\"));\n        }\n        if (suggestRestartProxy && NekoGui::dataStore->started_id >= 0 &&\n            QMessageBox::question(GetMessageBoxParent(), tr(\"Confirmation\"), tr(\"Settings changed, restart proxy?\")) == QMessageBox::StandardButton::Yes) {\n            neko_start(NekoGui::dataStore->started_id);\n        }\n        refresh_status();\n    }\n    if (info.contains(\"NeedRestart\")) {\n        auto n = QMessageBox::warning(GetMessageBoxParent(), tr(\"Settings changed\"), tr(\"Restart the program to take effect.\"), QMessageBox::Yes | QMessageBox::No);\n        if (n == QMessageBox::Yes) {\n            this->exit_reason = 2;\n            on_menu_exit_triggered();\n        }\n    }\n    //\n    if (info == \"RestartProgram\") {\n        this->exit_reason = 2;\n        on_menu_exit_triggered();\n    } else if (info == \"Raise\") {\n        ActivateWindow(this);\n    } else if (info == \"ClearConnectionList\") {\n        refresh_connection_list({});\n    }\n    // sender\n    if (sender == Dialog_DialogEditProfile) {\n        auto msg = info.split(\",\");\n        if (msg.contains(\"accept\")) {\n            refresh_proxy_list();\n            if (msg.contains(\"restart\")) {\n                if (QMessageBox::question(GetMessageBoxParent(), tr(\"Confirmation\"), tr(\"Settings changed, restart proxy?\")) == QMessageBox::StandardButton::Yes) {\n                    neko_start(NekoGui::dataStore->started_id);\n                }\n            }\n        }\n    } else if (sender == Dialog_DialogManageGroups) {\n        if (info.startsWith(\"refresh\")) {\n            this->refresh_groups();\n        }\n    } else if (sender == \"SubUpdater\") {\n        if (info.startsWith(\"finish\")) {\n            refresh_proxy_list();\n            if (!info.contains(\"dingyue\")) {\n                show_log_impl(tr(\"Imported %1 profile(s)\").arg(NekoGui::dataStore->imported_count));\n            }\n        } else if (info == \"NewGroup\") {\n            refresh_groups();\n        }\n    } else if (sender == \"ExternalProcess\") {\n        if (info == \"Crashed\") {\n            neko_stop();\n        } else if (info == \"CoreCrashed\") {\n            neko_stop(true);\n        } else if (info.startsWith(\"CoreStarted\")) {\n            neko_start(info.split(\",\")[1].toInt());\n        }\n    }\n}\n\n// top bar & tray menu\n\ninline bool dialog_is_using = false;\n\n#define USE_DIALOG(a)                               \\\n    if (dialog_is_using) return;                    \\\n    dialog_is_using = true;                         \\\n    auto dialog = new a(this);                      \\\n    connect(dialog, &QDialog::finished, this, [=] { \\\n        dialog->deleteLater();                      \\\n        dialog_is_using = false;                    \\\n    });                                             \\\n    dialog->show();\n\nvoid MainWindow::on_menu_basic_settings_triggered() {\n    USE_DIALOG(DialogBasicSettings)\n}\n\nvoid MainWindow::on_menu_manage_groups_triggered() {\n    USE_DIALOG(DialogManageGroups)\n}\n\nvoid MainWindow::on_menu_routing_settings_triggered() {\n    USE_DIALOG(DialogManageRoutes)\n}\n\nvoid MainWindow::on_menu_vpn_settings_triggered() {\n    USE_DIALOG(DialogVPNSettings)\n}\n\nvoid MainWindow::on_menu_hotkey_settings_triggered() {\n    USE_DIALOG(DialogHotkey)\n}\n\nvoid MainWindow::on_commitDataRequest() {\n    qDebug() << \"Start of data save\";\n    //\n    if (!isMaximized()) {\n        auto olds = NekoGui::dataStore->mw_size;\n        auto news = QStringLiteral(\"%1x%2\").arg(size().width()).arg(size().height());\n        if (olds != news) {\n            NekoGui::dataStore->mw_size = news;\n        }\n    }\n    //\n    NekoGui::dataStore->splitter_state = ui->splitter->saveState().toBase64();\n    //\n    auto last_id = NekoGui::dataStore->started_id;\n    if (NekoGui::dataStore->remember_enable && last_id >= 0) {\n        NekoGui::dataStore->remember_id = last_id;\n    }\n    //\n    NekoGui::dataStore->Save();\n    NekoGui::profileManager->SaveManager();\n    qDebug() << \"End of data save\";\n}\n\nvoid MainWindow::on_menu_exit_triggered() {\n    if (mu_exit.tryLock()) {\n        NekoGui::dataStore->prepare_exit = true;\n        //\n        neko_set_spmode_system_proxy(false, false);\n        neko_set_spmode_vpn(false, false);\n        if (NekoGui::dataStore->spmode_vpn) {\n            mu_exit.unlock(); // retry\n            return;\n        }\n        RegisterHotkey(true);\n        //\n        on_commitDataRequest();\n        //\n        NekoGui::dataStore->save_control_no_save = true; // don't change datastore after this line\n        neko_stop(false, true);\n        //\n        hide();\n        runOnNewThread([=] {\n            sem_stopped.acquire();\n            stop_core_daemon();\n            runOnUiThread([=] {\n                on_menu_exit_triggered(); // continue exit progress\n            });\n        });\n        return;\n    }\n    //\n    MF_release_runguard();\n    if (exit_reason == 1) {\n        QDir::setCurrent(QApplication::applicationDirPath());\n        QProcess::startDetached(\"./updater\", QStringList{});\n    } else if (exit_reason == 2 || exit_reason == 3) {\n        QDir::setCurrent(QApplication::applicationDirPath());\n\n        auto arguments = NekoGui::dataStore->argv;\n        if (arguments.length() > 0) {\n            arguments.removeFirst();\n            arguments.removeAll(\"-tray\");\n            arguments.removeAll(\"-flag_restart_tun_on\");\n            arguments.removeAll(\"-flag_reorder\");\n        }\n        auto isLauncher = qEnvironmentVariable(\"NKR_FROM_LAUNCHER\") == \"1\";\n        if (isLauncher) arguments.prepend(\"--\");\n        auto program = isLauncher ? \"./launcher\" : QApplication::applicationFilePath();\n\n        if (exit_reason == 3) {\n            // Tun restart as admin\n            arguments << \"-flag_restart_tun_on\";\n#ifdef Q_OS_WIN\n            WinCommander::runProcessElevated(program, arguments, \"\", WinCommander::SW_NORMAL, false);\n#else\n            QProcess::startDetached(program, arguments);\n#endif\n        } else {\n            QProcess::startDetached(program, arguments);\n        }\n    }\n    tray->hide();\n    QCoreApplication::quit();\n}\n\n#define neko_set_spmode_FAILED \\\n    refresh_status();          \\\n    return;\n\nvoid MainWindow::neko_set_spmode_system_proxy(bool enable, bool save) {\n    if (enable != NekoGui::dataStore->spmode_system_proxy) {\n        if (enable) {\n            auto socks_port = NekoGui::dataStore->inbound_socks_port;\n            auto http_port = NekoGui::dataStore->inbound_socks_port;\n            SetSystemProxy(http_port, socks_port);\n        } else {\n            ClearSystemProxy();\n        }\n    }\n\n    if (save) {\n        NekoGui::dataStore->remember_spmode.removeAll(\"system_proxy\");\n        if (enable && NekoGui::dataStore->remember_enable) {\n            NekoGui::dataStore->remember_spmode.append(\"system_proxy\");\n        }\n        NekoGui::dataStore->Save();\n    }\n\n    NekoGui::dataStore->spmode_system_proxy = enable;\n    refresh_status();\n}\n\nvoid MainWindow::neko_set_spmode_vpn(bool enable, bool save) {\n    if (enable != NekoGui::dataStore->spmode_vpn) {\n        if (enable) {\n            if (NekoGui::dataStore->vpn_internal_tun) {\n                bool requestPermission = !NekoGui::IsAdmin();\n                if (requestPermission) {\n#ifdef Q_OS_LINUX\n                    if (!Linux_HavePkexec()) {\n                        MessageBoxWarning(software_name, \"Please install \\\"pkexec\\\" first.\");\n                        neko_set_spmode_FAILED\n                    }\n                    auto ret = Linux_Pkexec_SetCapString(NekoGui::FindNekoBoxCoreRealPath(), \"cap_net_admin=ep\");\n                    if (ret == 0) {\n                        this->exit_reason = 3;\n                        on_menu_exit_triggered();\n                    } else {\n                        MessageBoxWarning(software_name, \"Setcap for Tun mode failed.\\n\\n1. You may canceled the dialog.\\n2. You may be using an incompatible environment like AppImage.\");\n                        if (QProcessEnvironment::systemEnvironment().contains(\"APPIMAGE\")) {\n                            MW_show_log(\"If you are using AppImage, it's impossible to start a Tun. Please use other package instead.\");\n                        }\n                    }\n#endif\n#ifdef Q_OS_WIN\n                    auto n = QMessageBox::warning(GetMessageBoxParent(), software_name, tr(\"Please run NekoBox as admin\"), QMessageBox::Yes | QMessageBox::No);\n                    if (n == QMessageBox::Yes) {\n                        this->exit_reason = 3;\n                        on_menu_exit_triggered();\n                    }\n#endif\n                    neko_set_spmode_FAILED\n                }\n            } else {\n                if (NekoGui::dataStore->need_keep_vpn_off) {\n                    MessageBoxWarning(software_name, tr(\"Current server is incompatible with Tun. Please stop the server first, enable Tun Mode, and then restart.\"));\n                    neko_set_spmode_FAILED\n                }\n                if (!StartVPNProcess()) {\n                    neko_set_spmode_FAILED\n                }\n            }\n        } else {\n            if (NekoGui::dataStore->vpn_internal_tun) {\n                // current core is sing-box\n            } else {\n                if (!StopVPNProcess()) {\n                    neko_set_spmode_FAILED\n                }\n            }\n        }\n    }\n\n    if (save) {\n        NekoGui::dataStore->remember_spmode.removeAll(\"vpn\");\n        if (enable && NekoGui::dataStore->remember_enable) {\n            NekoGui::dataStore->remember_spmode.append(\"vpn\");\n        }\n        NekoGui::dataStore->Save();\n    }\n\n    NekoGui::dataStore->spmode_vpn = enable;\n    refresh_status();\n\n    if (NekoGui::dataStore->vpn_internal_tun && NekoGui::dataStore->started_id >= 0) neko_start(NekoGui::dataStore->started_id);\n}\n\nvoid MainWindow::refresh_status(const QString &traffic_update) {\n    auto refresh_speed_label = [=] {\n        if (traffic_update_cache == \"\") {\n            ui->label_speed->setText(QObject::tr(\"Proxy: %1\\nDirect: %2\").arg(\"\", \"\"));\n        } else {\n            ui->label_speed->setText(traffic_update_cache);\n        }\n    };\n\n    // From TrafficLooper\n    if (!traffic_update.isEmpty()) {\n        traffic_update_cache = traffic_update;\n        if (traffic_update == \"STOP\") {\n            traffic_update_cache = \"\";\n        } else {\n            refresh_speed_label();\n            return;\n        }\n    }\n\n    refresh_speed_label();\n\n    // From UI\n    QString group_name;\n    if (running != nullptr) {\n        auto group = NekoGui::profileManager->GetGroup(running->gid);\n        if (group != nullptr) group_name = group->name;\n    }\n\n    if (last_test_time.addSecs(2) < QTime::currentTime()) {\n        auto txt = running == nullptr ? tr(\"Not Running\")\n                                      : QStringLiteral(\"[%1] %2\").arg(group_name, running->bean->DisplayName()).left(30);\n        ui->label_running->setText(txt);\n    }\n    //\n    auto display_socks = DisplayAddress(NekoGui::dataStore->inbound_address, NekoGui::dataStore->inbound_socks_port);\n    auto inbound_txt = QStringLiteral(\"Mixed: %1\").arg(display_socks);\n    ui->label_inbound->setText(inbound_txt);\n    //\n    ui->checkBox_VPN->setChecked(NekoGui::dataStore->spmode_vpn);\n    ui->checkBox_SystemProxy->setChecked(NekoGui::dataStore->spmode_system_proxy);\n    if (select_mode) {\n        ui->label_running->setText(tr(\"Select\") + \" *\");\n        ui->label_running->setToolTip(tr(\"Select mode, double-click or press Enter to select a profile, press ESC to exit.\"));\n    } else {\n        ui->label_running->setToolTip({});\n    }\n\n    auto make_title = [=](bool isTray) {\n        QStringList tt;\n        if (!isTray && NekoGui::IsAdmin()) tt << \"[Admin]\";\n        if (select_mode) tt << \"[\" + tr(\"Select\") + \"]\";\n        if (!title_error.isEmpty()) tt << \"[\" + title_error + \"]\";\n        if (NekoGui::dataStore->spmode_vpn && !NekoGui::dataStore->spmode_system_proxy) tt << \"[Tun]\";\n        if (!NekoGui::dataStore->spmode_vpn && NekoGui::dataStore->spmode_system_proxy) tt << \"[\" + tr(\"System Proxy\") + \"]\";\n        if (NekoGui::dataStore->spmode_vpn && NekoGui::dataStore->spmode_system_proxy) tt << \"[Tun+\" + tr(\"System Proxy\") + \"]\";\n        tt << software_name;\n        if (!isTray) tt << \"(\" + QString(NKR_VERSION) + \")\";\n        if (!NekoGui::dataStore->active_routing.isEmpty() && NekoGui::dataStore->active_routing != \"Default\") {\n            tt << \"[\" + NekoGui::dataStore->active_routing + \"]\";\n        }\n        if (running != nullptr) tt << running->bean->DisplayTypeAndName() + \"@\" + group_name;\n        return tt.join(isTray ? \"\\n\" : \" \");\n    };\n\n    auto icon_status_new = Icon::NONE;\n\n    if (running != nullptr) {\n        if (NekoGui::dataStore->spmode_vpn) {\n            icon_status_new = Icon::VPN;\n        } else if (NekoGui::dataStore->spmode_system_proxy) {\n            icon_status_new = Icon::SYSTEM_PROXY;\n        } else {\n            icon_status_new = Icon::RUNNING;\n        }\n    }\n\n    // refresh title & window icon\n    setWindowTitle(make_title(false));\n    if (icon_status_new != icon_status) QApplication::setWindowIcon(Icon::GetTrayIcon(Icon::NONE));\n\n    // refresh tray\n    if (tray != nullptr) {\n        tray->setToolTip(make_title(true));\n        if (icon_status_new != icon_status) tray->setIcon(Icon::GetTrayIcon(icon_status_new));\n    }\n\n    icon_status = icon_status_new;\n}\n\n// table显示\n\n// refresh_groups -> show_group -> refresh_proxy_list\nvoid MainWindow::refresh_groups() {\n    NekoGui::dataStore->refreshing_group_list = true;\n\n    // refresh group?\n    for (int i = ui->tabWidget->count() - 1; i > 0; i--) {\n        ui->tabWidget->removeTab(i);\n    }\n\n    int index = 0;\n    for (const auto &gid: NekoGui::profileManager->groupsTabOrder) {\n        auto group = NekoGui::profileManager->GetGroup(gid);\n        if (index == 0) {\n            ui->tabWidget->setTabText(0, group->name);\n        } else {\n            auto widget2 = new QWidget();\n            auto layout2 = new QVBoxLayout();\n            layout2->setContentsMargins(QMargins());\n            layout2->setSpacing(0);\n            widget2->setLayout(layout2);\n            ui->tabWidget->addTab(widget2, group->name);\n        }\n        ui->tabWidget->tabBar()->setTabData(index, gid);\n        index++;\n    }\n\n    // show after group changed\n    if (NekoGui::profileManager->CurrentGroup() == nullptr) {\n        NekoGui::dataStore->current_group = -1;\n        ui->tabWidget->setCurrentIndex(groupId2TabIndex(0));\n        show_group(NekoGui::profileManager->groupsTabOrder.count() > 0 ? NekoGui::profileManager->groupsTabOrder.first() : 0);\n    } else {\n        ui->tabWidget->setCurrentIndex(groupId2TabIndex(NekoGui::dataStore->current_group));\n        show_group(NekoGui::dataStore->current_group);\n    }\n\n    NekoGui::dataStore->refreshing_group_list = false;\n}\n\nvoid MainWindow::refresh_proxy_list(const int &id) {\n    refresh_proxy_list_impl(id, {});\n}\n\nvoid MainWindow::refresh_proxy_list_impl(const int &id, GroupSortAction groupSortAction) {\n    // id < 0 重绘\n    if (id < 0) {\n        // 清空数据\n        ui->proxyListTable->row2Id.clear();\n        ui->proxyListTable->setRowCount(0);\n        // 添加行\n        int row = -1;\n        for (const auto &[id, profile]: NekoGui::profileManager->profiles) {\n            if (NekoGui::dataStore->current_group != profile->gid) continue;\n            row++;\n            ui->proxyListTable->insertRow(row);\n            ui->proxyListTable->row2Id += id;\n        }\n    }\n\n    // 显示排序\n    if (id < 0) {\n        switch (groupSortAction.method) {\n            case GroupSortMethod::Raw: {\n                auto group = NekoGui::profileManager->CurrentGroup();\n                if (group == nullptr) return;\n                ui->proxyListTable->order = group->order;\n                break;\n            }\n            case GroupSortMethod::ById: {\n                // Clear Order\n                ui->proxyListTable->order.clear();\n                ui->proxyListTable->callback_save_order();\n                break;\n            }\n            case GroupSortMethod::ByAddress:\n            case GroupSortMethod::ByName:\n            case GroupSortMethod::ByLatency:\n            case GroupSortMethod::ByType: {\n                std::sort(ui->proxyListTable->order.begin(), ui->proxyListTable->order.end(),\n                          [=](int a, int b) {\n                              QString ms_a;\n                              QString ms_b;\n                              if (groupSortAction.method == GroupSortMethod::ByType) {\n                                  ms_a = NekoGui::profileManager->GetProfile(a)->bean->DisplayType();\n                                  ms_b = NekoGui::profileManager->GetProfile(b)->bean->DisplayType();\n                              } else if (groupSortAction.method == GroupSortMethod::ByName) {\n                                  ms_a = NekoGui::profileManager->GetProfile(a)->bean->name;\n                                  ms_b = NekoGui::profileManager->GetProfile(b)->bean->name;\n                              } else if (groupSortAction.method == GroupSortMethod::ByAddress) {\n                                  ms_a = NekoGui::profileManager->GetProfile(a)->bean->DisplayAddress();\n                                  ms_b = NekoGui::profileManager->GetProfile(b)->bean->DisplayAddress();\n                              } else if (groupSortAction.method == GroupSortMethod::ByLatency) {\n                                  ms_a = NekoGui::profileManager->GetProfile(a)->full_test_report;\n                                  ms_b = NekoGui::profileManager->GetProfile(b)->full_test_report;\n                              }\n                              auto get_latency_for_sort = [](int id) {\n                                  auto i = NekoGui::profileManager->GetProfile(id)->latency;\n                                  if (i == 0) i = 100000;\n                                  if (i < 0) i = 99999;\n                                  return i;\n                              };\n                              if (groupSortAction.descending) {\n                                  if (groupSortAction.method == GroupSortMethod::ByLatency) {\n                                      if (ms_a.isEmpty() && ms_b.isEmpty()) {\n                                          // compare latency if full_test_report is empty\n                                          return get_latency_for_sort(a) > get_latency_for_sort(b);\n                                      }\n                                  }\n                                  return ms_a > ms_b;\n                              } else {\n                                  if (groupSortAction.method == GroupSortMethod::ByLatency) {\n                                      auto int_a = NekoGui::profileManager->GetProfile(a)->latency;\n                                      auto int_b = NekoGui::profileManager->GetProfile(b)->latency;\n                                      if (ms_a.isEmpty() && ms_b.isEmpty()) {\n                                          // compare latency if full_test_report is empty\n                                          return get_latency_for_sort(a) < get_latency_for_sort(b);\n                                      }\n                                  }\n                                  return ms_a < ms_b;\n                              }\n                          });\n                break;\n            }\n        }\n        ui->proxyListTable->update_order(groupSortAction.save_sort);\n    }\n\n    // refresh data\n    refresh_proxy_list_impl_refresh_data(id);\n}\n\nvoid MainWindow::refresh_proxy_list_impl_refresh_data(const int &id) {\n    // 绘制或更新item(s)\n    for (int row = 0; row < ui->proxyListTable->rowCount(); row++) {\n        auto profileId = ui->proxyListTable->row2Id[row];\n        if (id >= 0 && profileId != id) continue; // refresh ONE item\n        auto profile = NekoGui::profileManager->GetProfile(profileId);\n        if (profile == nullptr) continue;\n\n        auto isRunning = profileId == NekoGui::dataStore->started_id;\n        auto f0 = std::make_unique<QTableWidgetItem>();\n        f0->setData(114514, profileId);\n\n        // Check state\n        auto check = f0->clone();\n        check->setText(isRunning ? \"✓\" : Int2String(row + 1));\n        ui->proxyListTable->setVerticalHeaderItem(row, check);\n\n        // C0: Type\n        auto f = f0->clone();\n        f->setText(profile->bean->DisplayType());\n        if (isRunning) f->setForeground(palette().link());\n        ui->proxyListTable->setItem(row, 0, f);\n\n        // C1: Address+Port\n        f = f0->clone();\n        f->setText(profile->bean->DisplayAddress());\n        if (isRunning) f->setForeground(palette().link());\n        ui->proxyListTable->setItem(row, 1, f);\n\n        // C2: Name\n        f = f0->clone();\n        f->setText(profile->bean->name);\n        if (isRunning) f->setForeground(palette().link());\n        ui->proxyListTable->setItem(row, 2, f);\n\n        // C3: Test Result\n        f = f0->clone();\n        if (profile->full_test_report.isEmpty()) {\n            auto color = profile->DisplayLatencyColor();\n            if (color.isValid()) f->setForeground(color);\n            f->setText(profile->DisplayLatency());\n        } else {\n            f->setText(profile->full_test_report);\n        }\n        ui->proxyListTable->setItem(row, 3, f);\n\n        // C4: Traffic\n        f = f0->clone();\n        f->setText(profile->traffic_data->DisplayTraffic());\n        ui->proxyListTable->setItem(row, 4, f);\n    }\n}\n\n// table菜单相关\n\nvoid MainWindow::on_proxyListTable_itemDoubleClicked(QTableWidgetItem *item) {\n    auto id = item->data(114514).toInt();\n    if (select_mode) {\n        emit profile_selected(id);\n        select_mode = false;\n        refresh_status();\n        return;\n    }\n    auto dialog = new DialogEditProfile(\"\", id, this);\n    connect(dialog, &QDialog::finished, dialog, &QDialog::deleteLater);\n}\n\nvoid MainWindow::on_menu_add_from_input_triggered() {\n    auto dialog = new DialogEditProfile(\"socks\", NekoGui::dataStore->current_group, this);\n    connect(dialog, &QDialog::finished, dialog, &QDialog::deleteLater);\n}\n\nvoid MainWindow::on_menu_add_from_clipboard_triggered() {\n    auto clipboard = QApplication::clipboard()->text();\n    NekoGui_sub::groupUpdater->AsyncUpdate(clipboard);\n}\n\nvoid MainWindow::on_menu_clone_triggered() {\n    auto ents = get_now_selected_list();\n    if (ents.isEmpty()) return;\n\n    auto btn = QMessageBox::question(this, tr(\"Clone\"), tr(\"Clone %1 item(s)\").arg(ents.count()));\n    if (btn != QMessageBox::Yes) return;\n\n    QStringList sls;\n    for (const auto &ent: ents) {\n        sls << ent->bean->ToNekorayShareLink(ent->type);\n    }\n\n    NekoGui_sub::groupUpdater->AsyncUpdate(sls.join(\"\\n\"));\n}\n\nvoid MainWindow::on_menu_move_triggered() {\n    auto ents = get_now_selected_list();\n    if (ents.isEmpty()) return;\n\n    auto items = QStringList{};\n    for (auto gid: NekoGui::profileManager->groupsTabOrder) {\n        auto group = NekoGui::profileManager->GetGroup(gid);\n        if (group == nullptr) continue;\n        items += Int2String(gid) + \" \" + group->name;\n    }\n\n    bool ok;\n    auto a = QInputDialog::getItem(nullptr,\n                                   tr(\"Move\"),\n                                   tr(\"Move %1 item(s)\").arg(ents.count()),\n                                   items, 0, false, &ok);\n    if (!ok) return;\n    auto gid = SubStrBefore(a, \" \").toInt();\n    for (const auto &ent: ents) {\n        NekoGui::profileManager->MoveProfile(ent, gid);\n    }\n    refresh_proxy_list();\n}\n\nvoid MainWindow::on_menu_delete_triggered() {\n    auto ents = get_now_selected_list();\n    if (ents.count() == 0) return;\n    if (QMessageBox::question(this, tr(\"Confirmation\"), QString(tr(\"Remove %1 item(s) ?\")).arg(ents.count())) ==\n        QMessageBox::StandardButton::Yes) {\n        for (const auto &ent: ents) {\n            NekoGui::profileManager->DeleteProfile(ent->id);\n        }\n        refresh_proxy_list();\n    }\n}\n\nvoid MainWindow::on_menu_reset_traffic_triggered() {\n    auto ents = get_now_selected_list();\n    if (ents.count() == 0) return;\n    for (const auto &ent: ents) {\n        ent->traffic_data->Reset();\n        ent->Save();\n        refresh_proxy_list(ent->id);\n    }\n}\n\nvoid MainWindow::on_menu_profile_debug_info_triggered() {\n    auto ents = get_now_selected_list();\n    if (ents.count() != 1) return;\n    auto btn = QMessageBox::information(this, software_name, ents.first()->ToJsonBytes(), \"OK\", \"Edit\", \"Reload\", 0, 0);\n    if (btn == 1) {\n        QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(QStringLiteral(\"profiles/%1.json\").arg(ents.first()->id)).absoluteFilePath()));\n    } else if (btn == 2) {\n        NekoGui::dataStore->Load();\n        NekoGui::profileManager->LoadManager();\n        refresh_proxy_list();\n    }\n}\n\nvoid MainWindow::on_menu_copy_links_triggered() {\n    if (ui->masterLogBrowser->hasFocus()) {\n        ui->masterLogBrowser->copy();\n        return;\n    }\n    auto ents = get_now_selected_list();\n    QStringList links;\n    for (const auto &ent: ents) {\n        links += ent->bean->ToShareLink();\n    }\n    if (links.length() == 0) return;\n    QApplication::clipboard()->setText(links.join(\"\\n\"));\n    show_log_impl(tr(\"Copied %1 item(s)\").arg(links.length()));\n}\n\nvoid MainWindow::on_menu_copy_links_nkr_triggered() {\n    auto ents = get_now_selected_list();\n    QStringList links;\n    for (const auto &ent: ents) {\n        links += ent->bean->ToNekorayShareLink(ent->type);\n    }\n    if (links.length() == 0) return;\n    QApplication::clipboard()->setText(links.join(\"\\n\"));\n    show_log_impl(tr(\"Copied %1 item(s)\").arg(links.length()));\n}\n\nvoid MainWindow::on_menu_export_config_triggered() {\n    auto ents = get_now_selected_list();\n    if (ents.count() != 1) return;\n    auto ent = ents.first();\n    if (ent->bean->DisplayCoreType() != software_core_name) return;\n\n    auto result = BuildConfig(ent, false, true);\n    QString config_core = QJsonObject2QString(result->coreConfig, false);\n    QApplication::clipboard()->setText(config_core);\n\n    QMessageBox msg(QMessageBox::Information, tr(\"Config copied\"), tr(\"Config copied\"));\n    msg.addButton(\"Copy core config\", QMessageBox::YesRole);\n    msg.addButton(\"Copy test config\", QMessageBox::NoRole);\n    msg.addButton(QMessageBox::Ok);\n    msg.setEscapeButton(QMessageBox::Ok);\n    msg.setDefaultButton(QMessageBox::Ok);\n    auto ret = msg.exec();\n    if (ret == 2) {\n        result = BuildConfig(ent, false, false);\n        config_core = QJsonObject2QString(result->coreConfig, false);\n        QApplication::clipboard()->setText(config_core);\n    } else if (ret == 3) {\n        result = BuildConfig(ent, true, false);\n        config_core = QJsonObject2QString(result->coreConfig, false);\n        QApplication::clipboard()->setText(config_core);\n    }\n}\n\nvoid MainWindow::display_qr_link(bool nkrFormat) {\n    auto ents = get_now_selected_list();\n    if (ents.count() != 1) return;\n\n    class W : public QDialog {\n    public:\n        QLabel *l = nullptr;\n        QCheckBox *cb = nullptr;\n        //\n        QPlainTextEdit *l2 = nullptr;\n        QImage im;\n        //\n        QString link;\n        QString link_nk;\n\n        void show_qr(const QSize &size) const {\n            auto side = size.height() - 20 - l2->size().height() - cb->size().height();\n            l->setPixmap(QPixmap::fromImage(im.scaled(side, side, Qt::KeepAspectRatio, Qt::FastTransformation),\n                                            Qt::MonoOnly));\n            l->resize(side, side);\n        }\n\n        void refresh(bool is_nk) {\n            auto link_display = is_nk ? link_nk : link;\n            l2->setPlainText(link_display);\n            constexpr qint32 qr_padding = 2;\n            //\n            try {\n                qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(link_display.toUtf8().data(), qrcodegen::QrCode::Ecc::MEDIUM);\n                qint32 sz = qr.getSize();\n                im = QImage(sz + qr_padding * 2, sz + qr_padding * 2, QImage::Format_RGB32);\n                QRgb black = qRgb(0, 0, 0);\n                QRgb white = qRgb(255, 255, 255);\n                im.fill(white);\n                for (int y = 0; y < sz; y++)\n                    for (int x = 0; x < sz; x++)\n                        if (qr.getModule(x, y))\n                            im.setPixel(x + qr_padding, y + qr_padding, black);\n                show_qr(size());\n            } catch (const std::exception &ex) {\n                QMessageBox::warning(nullptr, \"error\", ex.what());\n            }\n        }\n\n        W(const QString &link_, const QString &link_nk_) {\n            link = link_;\n            link_nk = link_nk_;\n            //\n            setLayout(new QVBoxLayout);\n            setMinimumSize(256, 256);\n            QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);\n            sizePolicy.setHeightForWidth(true);\n            setSizePolicy(sizePolicy);\n            //\n            l = new QLabel();\n            l->setMinimumSize(256, 256);\n            l->setMargin(6);\n            l->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);\n            l->setScaledContents(true);\n            layout()->addWidget(l);\n            cb = new QCheckBox;\n            cb->setText(\"Neko Links\");\n            layout()->addWidget(cb);\n            l2 = new QPlainTextEdit();\n            l2->setReadOnly(true);\n            layout()->addWidget(l2);\n            //\n            connect(cb, &QCheckBox::toggled, this, &W::refresh);\n            refresh(false);\n        }\n\n        void resizeEvent(QResizeEvent *resizeEvent) override {\n            show_qr(resizeEvent->size());\n        }\n    };\n\n    auto link = ents.first()->bean->ToShareLink();\n    auto link_nk = ents.first()->bean->ToNekorayShareLink(ents.first()->type);\n    auto w = new W(link, link_nk);\n    w->setWindowTitle(ents.first()->bean->DisplayTypeAndName());\n    w->exec();\n    w->deleteLater();\n}\n\nvoid MainWindow::on_menu_scan_qr_triggered() {\n#ifndef NKR_NO_ZXING\n    using namespace ZXingQt;\n\n    hide();\n    QThread::sleep(1);\n\n    auto screen = QGuiApplication::primaryScreen();\n    auto geom = screen->geometry();\n    auto qpx = screen->grabWindow(0, geom.x(), geom.y(), geom.width(), geom.height());\n\n    show();\n\n    auto hints = DecodeHints()\n                     .setFormats(BarcodeFormat::QRCode)\n                     .setTryRotate(false)\n                     .setBinarizer(Binarizer::FixedThreshold);\n\n    auto result = ReadBarcode(qpx.toImage(), hints);\n    const auto &text = result.text();\n    if (text.isEmpty()) {\n        MessageBoxInfo(software_name, tr(\"QR Code not found\"));\n    } else {\n        show_log_impl(\"QR Code Result:\\n\" + text);\n        NekoGui_sub::groupUpdater->AsyncUpdate(text);\n    }\n#endif\n}\n\nvoid MainWindow::on_menu_clear_test_result_triggered() {\n    for (const auto &profile: get_selected_or_group()) {\n        profile->latency = 0;\n        profile->full_test_report = \"\";\n        profile->Save();\n    }\n    refresh_proxy_list();\n}\n\nvoid MainWindow::on_menu_select_all_triggered() {\n    if (ui->masterLogBrowser->hasFocus()) {\n        ui->masterLogBrowser->selectAll();\n        return;\n    }\n    ui->proxyListTable->selectAll();\n}\n\nvoid MainWindow::on_menu_delete_repeat_triggered() {\n    QList<std::shared_ptr<NekoGui::ProxyEntity>> out;\n    QList<std::shared_ptr<NekoGui::ProxyEntity>> out_del;\n\n    NekoGui::ProfileFilter::Uniq(NekoGui::profileManager->CurrentGroup()->Profiles(), out, true, false);\n    NekoGui::ProfileFilter::OnlyInSrc_ByPointer(NekoGui::profileManager->CurrentGroup()->Profiles(), out, out_del);\n\n    int remove_display_count = 0;\n    QString remove_display;\n    for (const auto &ent: out_del) {\n        remove_display += ent->bean->DisplayTypeAndName() + \"\\n\";\n        if (++remove_display_count == 20) {\n            remove_display += \"...\";\n            break;\n        }\n    }\n\n    if (out_del.length() > 0 &&\n        QMessageBox::question(this, tr(\"Confirmation\"), tr(\"Remove %1 item(s) ?\").arg(out_del.length()) + \"\\n\" + remove_display) == QMessageBox::StandardButton::Yes) {\n        for (const auto &ent: out_del) {\n            NekoGui::profileManager->DeleteProfile(ent->id);\n        }\n        refresh_proxy_list();\n    }\n}\n\nbool mw_sub_updating = false;\n\nvoid MainWindow::on_menu_update_subscription_triggered() {\n    auto group = NekoGui::profileManager->CurrentGroup();\n    if (group->url.isEmpty()) return;\n    if (mw_sub_updating) return;\n    mw_sub_updating = true;\n    NekoGui_sub::groupUpdater->AsyncUpdate(group->url, group->id, [&] { mw_sub_updating = false; });\n}\n\nvoid MainWindow::on_menu_remove_unavailable_triggered() {\n    QList<std::shared_ptr<NekoGui::ProxyEntity>> out_del;\n\n    for (const auto &[_, profile]: NekoGui::profileManager->profiles) {\n        if (NekoGui::dataStore->current_group != profile->gid) continue;\n        if (profile->latency < 0) out_del += profile;\n    }\n\n    int remove_display_count = 0;\n    QString remove_display;\n    for (const auto &ent: out_del) {\n        remove_display += ent->bean->DisplayTypeAndName() + \"\\n\";\n        if (++remove_display_count == 20) {\n            remove_display += \"...\";\n            break;\n        }\n    }\n\n    if (out_del.length() > 0 &&\n        QMessageBox::question(this, tr(\"Confirmation\"), tr(\"Remove %1 item(s) ?\").arg(out_del.length()) + \"\\n\" + remove_display) == QMessageBox::StandardButton::Yes) {\n        for (const auto &ent: out_del) {\n            NekoGui::profileManager->DeleteProfile(ent->id);\n        }\n        refresh_proxy_list();\n    }\n}\n\nvoid MainWindow::on_menu_resolve_domain_triggered() {\n    auto profiles = get_selected_or_group();\n    if (profiles.isEmpty()) return;\n\n    if (QMessageBox::question(this,\n                              tr(\"Confirmation\"),\n                              tr(\"Resolving domain to IP, if support.\")) != QMessageBox::StandardButton::Yes) {\n        return;\n    }\n    if (mw_sub_updating) return;\n    mw_sub_updating = true;\n    NekoGui::dataStore->resolve_count = profiles.count();\n\n    for (const auto &profile: profiles) {\n        profile->bean->ResolveDomainToIP([=] {\n            profile->Save();\n            if (--NekoGui::dataStore->resolve_count != 0) return;\n            refresh_proxy_list();\n            mw_sub_updating = false;\n        });\n    }\n}\n\nvoid MainWindow::on_proxyListTable_customContextMenuRequested(const QPoint &pos) {\n    ui->menu_server->popup(ui->proxyListTable->viewport()->mapToGlobal(pos)); // 弹出菜单\n}\n\nQList<std::shared_ptr<NekoGui::ProxyEntity>> MainWindow::get_now_selected_list() {\n    auto items = ui->proxyListTable->selectedItems();\n    QList<std::shared_ptr<NekoGui::ProxyEntity>> list;\n    for (auto item: items) {\n        auto id = item->data(114514).toInt();\n        auto ent = NekoGui::profileManager->GetProfile(id);\n        if (ent != nullptr && !list.contains(ent)) list += ent;\n    }\n    return list;\n}\n\nQList<std::shared_ptr<NekoGui::ProxyEntity>> MainWindow::get_selected_or_group() {\n    auto selected_or_group = ui->menu_server->property(\"selected_or_group\").toInt();\n    QList<std::shared_ptr<NekoGui::ProxyEntity>> profiles;\n    if (selected_or_group > 0) {\n        profiles = get_now_selected_list();\n        if (profiles.isEmpty() && selected_or_group == 2) profiles = NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder();\n    } else {\n        profiles = NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder();\n    }\n    return profiles;\n}\n\nvoid MainWindow::keyPressEvent(QKeyEvent *event) {\n    switch (event->key()) {\n        case Qt::Key_Escape:\n            // take over by shortcut_esc\n            break;\n        case Qt::Key_Enter:\n            neko_start();\n            break;\n        default:\n            QMainWindow::keyPressEvent(event);\n    }\n}\n\n// Log\n\ninline void FastAppendTextDocument(const QString &message, QTextDocument *doc) {\n    QTextCursor cursor(doc);\n    cursor.movePosition(QTextCursor::End);\n    cursor.beginEditBlock();\n    cursor.insertBlock();\n    cursor.insertText(message);\n    cursor.endEditBlock();\n}\n\nvoid MainWindow::show_log_impl(const QString &log) {\n    auto lines = SplitLines(log.trimmed());\n    if (lines.isEmpty()) return;\n\n    QStringList newLines;\n    auto log_ignore = NekoGui::dataStore->log_ignore;\n    for (const auto &line: lines) {\n        bool showThisLine = true;\n        for (const auto &str: log_ignore) {\n            if (line.contains(str)) {\n                showThisLine = false;\n                break;\n            }\n        }\n        if (showThisLine) newLines << line;\n    }\n    if (newLines.isEmpty()) return;\n\n    FastAppendTextDocument(newLines.join(\"\\n\"), qvLogDocument);\n    // qvLogDocument->setPlainText(qvLogDocument->toPlainText() + log);\n    // From https://gist.github.com/jemyzhang/7130092\n    auto block = qvLogDocument->begin();\n\n    while (block.isValid()) {\n        if (qvLogDocument->blockCount() > NekoGui::dataStore->max_log_line) {\n            QTextCursor cursor(block);\n            block = block.next();\n            cursor.select(QTextCursor::BlockUnderCursor);\n            cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);\n            cursor.removeSelectedText();\n            continue;\n        }\n        break;\n    }\n}\n\n#define ADD_TO_CURRENT_ROUTE(a, b)                                                                   \\\n    NekoGui::dataStore->routing->a = (SplitLines(NekoGui::dataStore->routing->a) << (b)).join(\"\\n\"); \\\n    NekoGui::dataStore->routing->Save();\n\nvoid MainWindow::on_masterLogBrowser_customContextMenuRequested(const QPoint &pos) {\n    QMenu *menu = ui->masterLogBrowser->createStandardContextMenu();\n\n    auto sep = new QAction(this);\n    sep->setSeparator(true);\n    menu->addAction(sep);\n\n    auto action_add_ignore = new QAction(this);\n    action_add_ignore->setText(tr(\"Set ignore keyword\"));\n    connect(action_add_ignore, &QAction::triggered, this, [=] {\n        auto list = NekoGui::dataStore->log_ignore;\n        auto newStr = ui->masterLogBrowser->textCursor().selectedText().trimmed();\n        if (!newStr.isEmpty()) list << newStr;\n        bool ok;\n        newStr = QInputDialog::getMultiLineText(GetMessageBoxParent(), tr(\"Set ignore keyword\"), tr(\"Set the following keywords to ignore?\\nSplit by line.\"), list.join(\"\\n\"), &ok);\n        if (ok) {\n            NekoGui::dataStore->log_ignore = SplitLines(newStr);\n            NekoGui::dataStore->Save();\n        }\n    });\n    menu->addAction(action_add_ignore);\n\n    auto action_add_route = new QAction(this);\n    action_add_route->setText(tr(\"Save as route\"));\n    connect(action_add_route, &QAction::triggered, this, [=] {\n        auto newStr = ui->masterLogBrowser->textCursor().selectedText().trimmed();\n        if (newStr.isEmpty()) return;\n        //\n        bool ok;\n        newStr = QInputDialog::getText(GetMessageBoxParent(), tr(\"Save as route\"), tr(\"Edit\"), {}, newStr, &ok).trimmed();\n        if (!ok) return;\n        if (newStr.isEmpty()) return;\n        //\n        auto select = IsIpAddress(newStr) ? 0 : 3;\n        QStringList items = {\"proxyIP\", \"bypassIP\", \"blockIP\", \"proxyDomain\", \"bypassDomain\", \"blockDomain\"};\n        auto item = QInputDialog::getItem(GetMessageBoxParent(), tr(\"Save as route\"),\n                                          tr(\"Save \\\"%1\\\" as a routing rule?\").arg(newStr),\n                                          items, select, false, &ok);\n        if (ok) {\n            auto index = items.indexOf(item);\n            switch (index) {\n                case 0:\n                    ADD_TO_CURRENT_ROUTE(proxy_ip, newStr);\n                    break;\n                case 1:\n                    ADD_TO_CURRENT_ROUTE(direct_ip, newStr);\n                    break;\n                case 2:\n                    ADD_TO_CURRENT_ROUTE(block_ip, newStr);\n                    break;\n                case 3:\n                    ADD_TO_CURRENT_ROUTE(proxy_domain, newStr);\n                    break;\n                case 4:\n                    ADD_TO_CURRENT_ROUTE(direct_domain, newStr);\n                    break;\n                case 5:\n                    ADD_TO_CURRENT_ROUTE(block_domain, newStr);\n                    break;\n                default:\n                    break;\n            }\n            MW_dialog_message(\"\", \"UpdateDataStore,RouteChanged\");\n        }\n    });\n    menu->addAction(action_add_route);\n\n    auto action_clear = new QAction(this);\n    action_clear->setText(tr(\"Clear\"));\n    connect(action_clear, &QAction::triggered, this, [=] {\n        qvLogDocument->clear();\n        ui->masterLogBrowser->clear();\n    });\n    menu->addAction(action_clear);\n\n    menu->exec(ui->masterLogBrowser->viewport()->mapToGlobal(pos)); // 弹出菜单\n}\n\n// eventFilter\n\nbool MainWindow::eventFilter(QObject *obj, QEvent *event) {\n    if (event->type() == QEvent::MouseButtonPress) {\n        auto mouseEvent = dynamic_cast<QMouseEvent *>(event);\n        if (obj == ui->label_running && mouseEvent->button() == Qt::LeftButton && running != nullptr) {\n            speedtest_current();\n            return true;\n        } else if (obj == ui->label_inbound && mouseEvent->button() == Qt::LeftButton) {\n            on_menu_basic_settings_triggered();\n            return true;\n        }\n    } else if (event->type() == QEvent::MouseButtonDblClick) {\n        if (obj == ui->splitter) {\n            auto size = ui->splitter->size();\n            ui->splitter->setSizes({size.height() / 2, size.height() / 2});\n        }\n    }\n    return QMainWindow::eventFilter(obj, event);\n}\n\n// profile selector\n\nvoid MainWindow::start_select_mode(QObject *context, const std::function<void(int)> &callback) {\n    select_mode = true;\n    connectOnce(this, &MainWindow::profile_selected, context, callback);\n    refresh_status();\n}\n\n// 连接列表\n\ninline QJsonArray last_arr; // format is nekoray_connections_json\n\nvoid MainWindow::refresh_connection_list(const QJsonArray &arr) {\n    if (last_arr == arr) {\n        return;\n    }\n    last_arr = arr;\n\n    if (NekoGui::dataStore->flag_debug) qDebug() << arr;\n\n    ui->tableWidget_conn->setRowCount(0);\n\n    int row = -1;\n    for (const auto &_item: arr) {\n        auto item = _item.toObject();\n        if (NekoGui::dataStore->ignoreConnTag.contains(item[\"Tag\"].toString())) continue;\n\n        row++;\n        ui->tableWidget_conn->insertRow(row);\n\n        auto f0 = std::make_unique<QTableWidgetItem>();\n        f0->setData(114514, item[\"ID\"].toInt());\n\n        // C0: Status\n        auto c0 = new QLabel;\n        auto start_t = item[\"Start\"].toInt();\n        auto end_t = item[\"End\"].toInt();\n        // icon\n        auto outboundTag = item[\"Tag\"].toString();\n        if (outboundTag == \"block\") {\n            c0->setPixmap(Icon::GetMaterialIcon(\"cancel\"));\n        } else {\n            if (end_t > 0) {\n                c0->setPixmap(Icon::GetMaterialIcon(\"history\"));\n            } else {\n                c0->setPixmap(Icon::GetMaterialIcon(\"swap-vertical\"));\n            }\n        }\n        c0->setAlignment(Qt::AlignCenter);\n        c0->setToolTip(tr(\"Start: %1\\nEnd: %2\").arg(DisplayTime(start_t), end_t > 0 ? DisplayTime(end_t) : \"\"));\n        ui->tableWidget_conn->setCellWidget(row, 0, c0);\n\n        // C1: Outbound\n        auto f = f0->clone();\n        f->setToolTip(\"\");\n        f->setText(outboundTag);\n        ui->tableWidget_conn->setItem(row, 1, f);\n\n        // C2: Destination\n        f = f0->clone();\n        QString target1 = item[\"Dest\"].toString();\n        QString target2 = item[\"RDest\"].toString();\n        if (target2.isEmpty() || target1 == target2) {\n            target2 = \"\";\n        }\n        f->setText(\"[\" + target1 + \"] \" + target2);\n        ui->tableWidget_conn->setItem(row, 2, f);\n    }\n}\n\n// Hotkey\n\n#ifndef NKR_NO_QHOTKEY\n\n#include <QHotkey>\n\ninline QList<std::shared_ptr<QHotkey>> RegisteredHotkey;\n\nvoid MainWindow::RegisterHotkey(bool unregister) {\n    while (!RegisteredHotkey.isEmpty()) {\n        auto hk = RegisteredHotkey.takeFirst();\n        hk->deleteLater();\n    }\n    if (unregister) return;\n\n    QStringList regstr{\n        NekoGui::dataStore->hotkey_mainwindow,\n        NekoGui::dataStore->hotkey_group,\n        NekoGui::dataStore->hotkey_route,\n        NekoGui::dataStore->hotkey_system_proxy_menu,\n    };\n\n    for (const auto &key: regstr) {\n        if (key.isEmpty()) continue;\n        if (regstr.count(key) > 1) return; // Conflict hotkey\n    }\n    for (const auto &key: regstr) {\n        QKeySequence k(key);\n        if (k.isEmpty()) continue;\n        auto hk = std::make_shared<QHotkey>(k, true);\n        if (hk->isRegistered()) {\n            RegisteredHotkey += hk;\n            connect(hk.get(), &QHotkey::activated, this, [=] { HotkeyEvent(key); });\n        } else {\n            hk->deleteLater();\n        }\n    }\n}\n\nvoid MainWindow::HotkeyEvent(const QString &key) {\n    if (key.isEmpty()) return;\n    runOnUiThread([=] {\n        if (key == NekoGui::dataStore->hotkey_mainwindow) {\n            tray->activated(QSystemTrayIcon::ActivationReason::Trigger);\n        } else if (key == NekoGui::dataStore->hotkey_group) {\n            on_menu_manage_groups_triggered();\n        } else if (key == NekoGui::dataStore->hotkey_route) {\n            on_menu_routing_settings_triggered();\n        } else if (key == NekoGui::dataStore->hotkey_system_proxy_menu) {\n            ui->menu_spmode->popup(QCursor::pos());\n        }\n    });\n}\n\n#else\n\nvoid MainWindow::RegisterHotkey(bool unregister) {}\n\nvoid MainWindow::HotkeyEvent(const QString &key) {}\n\n#endif\n\n// VPN Launcher\n\nbool MainWindow::StartVPNProcess() {\n    //\n    if (vpn_pid != 0) {\n        return true;\n    }\n    //\n    auto configPath = NekoGui::WriteVPNSingBoxConfig();\n    auto scriptPath = NekoGui::WriteVPNLinuxScript(configPath);\n    //\n#ifdef Q_OS_WIN\n    runOnNewThread([=] {\n        vpn_pid = 1; // TODO get pid?\n        WinCommander::runProcessElevated(QApplication::applicationDirPath() + \"/nekobox_core.exe\",\n                                         {\"--disable-color\", \"run\", \"-c\", configPath}, \"\",\n                                         NekoGui::dataStore->vpn_hide_console ? WinCommander::SW_HIDE : WinCommander::SW_SHOWMINIMIZED); // blocking\n        vpn_pid = 0;\n        runOnUiThread([=] { neko_set_spmode_vpn(false); });\n    });\n#else\n    //\n    auto vpn_process = new QProcess;\n    QProcess::connect(vpn_process, &QProcess::stateChanged, this, [=](QProcess::ProcessState state) {\n        if (state == QProcess::NotRunning) {\n            vpn_pid = 0;\n            vpn_process->deleteLater();\n            GetMainWindow()->neko_set_spmode_vpn(false);\n        }\n    });\n    //\n    vpn_process->setProcessChannelMode(QProcess::ForwardedChannels);\n#ifdef Q_OS_MACOS\n    vpn_process->start(\"osascript\", {\"-e\", QStringLiteral(\"do shell script \\\"%1\\\" with administrator privileges\")\n                                               .arg(\"bash \" + scriptPath)});\n#else\n    vpn_process->start(\"pkexec\", {\"bash\", scriptPath});\n#endif\n    vpn_process->waitForStarted();\n    vpn_pid = vpn_process->processId(); // actually it's pkexec or bash PID\n#endif\n    return true;\n}\n\nbool MainWindow::StopVPNProcess(bool unconditional) {\n    if (unconditional || vpn_pid != 0) {\n        bool ok;\n        core_process->processId();\n#ifdef Q_OS_WIN\n        auto ret = WinCommander::runProcessElevated(\"taskkill\", {\"/IM\", \"nekobox_core.exe\",\n                                                                 \"/FI\",\n                                                                 \"PID ne \" + Int2String(core_process->processId())});\n        ok = ret == 0;\n#else\n        QProcess p;\n#ifdef Q_OS_MACOS\n        p.start(\"osascript\", {\"-e\", QStringLiteral(\"do shell script \\\"%1\\\" with administrator privileges\")\n                                        .arg(\"pkill -2 -U 0 nekobox_core\")});\n#else\n        if (unconditional) {\n            p.start(\"pkexec\", {\"killall\", \"-2\", \"nekobox_core\"});\n        } else {\n            p.start(\"pkexec\", {\"pkill\", \"-2\", \"-P\", Int2String(vpn_pid)});\n        }\n#endif\n        p.waitForFinished();\n        ok = p.exitCode() == 0;\n#endif\n        if (!unconditional) {\n            ok ? vpn_pid = 0 : MessageBoxWarning(tr(\"Error\"), tr(\"Failed to stop Tun process\"));\n        }\n        return ok;\n    }\n    return true;\n}\n"
  },
  {
    "path": "ui/mainwindow.h",
    "content": "#pragma once\n\n#include <QMainWindow>\n\n#include \"main/NekoGui.hpp\"\n\n#ifndef MW_INTERFACE\n\n#include <QTime>\n#include <QTableWidgetItem>\n#include <QKeyEvent>\n#include <QSystemTrayIcon>\n#include <QProcess>\n#include <QTextDocument>\n#include <QShortcut>\n#include <QSemaphore>\n#include <QMutex>\n\n#include \"GroupSort.hpp\"\n\n#include \"db/ProxyEntity.hpp\"\n#include \"main/GuiUtils.hpp\"\n\n#endif\n\nnamespace NekoGui_sys {\n    class CoreProcess;\n}\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class MainWindow;\n}\nQT_END_NAMESPACE\n\nclass MainWindow : public QMainWindow {\n    Q_OBJECT\n\npublic:\n    explicit MainWindow(QWidget *parent = nullptr);\n\n    ~MainWindow() override;\n\n    void refresh_proxy_list(const int &id = -1);\n\n    void show_group(int gid);\n\n    void refresh_groups();\n\n    void refresh_status(const QString &traffic_update = \"\");\n\n    void neko_start(int _id = -1);\n\n    void neko_stop(bool crash = false, bool sem = false);\n\n    void neko_set_spmode_system_proxy(bool enable, bool save = true);\n\n    void neko_set_spmode_vpn(bool enable, bool save = true);\n\n    void show_log_impl(const QString &log);\n\n    void start_select_mode(QObject *context, const std::function<void(int)> &callback);\n\n    void refresh_connection_list(const QJsonArray &arr);\n\n    void RegisterHotkey(bool unregister);\n\n    bool StopVPNProcess(bool unconditional = false);\n\nsignals:\n\n    void profile_selected(int id);\n\npublic slots:\n\n    void on_commitDataRequest();\n\n    void on_menu_exit_triggered();\n\n#ifndef MW_INTERFACE\n\nprivate slots:\n\n    void on_masterLogBrowser_customContextMenuRequested(const QPoint &pos);\n\n    void on_menu_basic_settings_triggered();\n\n    void on_menu_routing_settings_triggered();\n\n    void on_menu_vpn_settings_triggered();\n\n    void on_menu_hotkey_settings_triggered();\n\n    void on_menu_add_from_input_triggered();\n\n    void on_menu_add_from_clipboard_triggered();\n\n    void on_menu_clone_triggered();\n\n    void on_menu_move_triggered();\n\n    void on_menu_delete_triggered();\n\n    void on_menu_reset_traffic_triggered();\n\n    void on_menu_profile_debug_info_triggered();\n\n    void on_menu_copy_links_triggered();\n\n    void on_menu_copy_links_nkr_triggered();\n\n    void on_menu_export_config_triggered();\n\n    void display_qr_link(bool nkrFormat = false);\n\n    void on_menu_scan_qr_triggered();\n\n    void on_menu_clear_test_result_triggered();\n\n    void on_menu_manage_groups_triggered();\n\n    void on_menu_select_all_triggered();\n\n    void on_menu_delete_repeat_triggered();\n\n    void on_menu_remove_unavailable_triggered();\n\n    void on_menu_update_subscription_triggered();\n\n    void on_menu_resolve_domain_triggered();\n\n    void on_proxyListTable_itemDoubleClicked(QTableWidgetItem *item);\n\n    void on_proxyListTable_customContextMenuRequested(const QPoint &pos);\n\n    void on_tabWidget_currentChanged(int index);\n\nprivate:\n    Ui::MainWindow *ui;\n    QSystemTrayIcon *tray;\n    QShortcut *shortcut_ctrl_f = new QShortcut(QKeySequence(\"Ctrl+F\"), this);\n    QShortcut *shortcut_esc = new QShortcut(QKeySequence(\"Esc\"), this);\n    //\n    NekoGui_sys::CoreProcess *core_process;\n    qint64 vpn_pid = 0;\n    //\n    bool qvLogAutoScoll = true;\n    QTextDocument *qvLogDocument = new QTextDocument(this);\n    //\n    QString title_error;\n    int icon_status = -1;\n    std::shared_ptr<NekoGui::ProxyEntity> running;\n    QString traffic_update_cache;\n    QTime last_test_time;\n    //\n    int proxy_last_order = -1;\n    bool select_mode = false;\n    QMutex mu_starting;\n    QMutex mu_stopping;\n    QMutex mu_exit;\n    QSemaphore sem_stopped;\n    int exit_reason = 0;\n\n    QList<std::shared_ptr<NekoGui::ProxyEntity>> get_now_selected_list();\n\n    QList<std::shared_ptr<NekoGui::ProxyEntity>> get_selected_or_group();\n\n    void dialog_message_impl(const QString &sender, const QString &info);\n\n    void refresh_proxy_list_impl(const int &id = -1, GroupSortAction groupSortAction = {});\n\n    void refresh_proxy_list_impl_refresh_data(const int &id = -1);\n\n    void keyPressEvent(QKeyEvent *event) override;\n\n    void closeEvent(QCloseEvent *event) override;\n\n    //\n\n    void HotkeyEvent(const QString &key);\n\n    bool StartVPNProcess();\n\n    // grpc and ...\n\n    static void setup_grpc();\n\n    void speedtest_current_group(int mode, bool test_group);\n\n    void speedtest_current();\n\n    static void stop_core_daemon();\n\n    void CheckUpdate();\n\nprotected:\n    bool eventFilter(QObject *obj, QEvent *event) override;\n\n#endif // MW_INTERFACE\n};\n\ninline MainWindow *GetMainWindow() {\n    return (MainWindow *) mainwindow;\n}\n\nvoid UI_InitMainWindow();\n"
  },
  {
    "path": "ui/mainwindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>MainWindow</class>\n <widget class=\"QMainWindow\" name=\"MainWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>800</width>\n    <height>600</height>\n   </rect>\n  </property>\n  <property name=\"minimumSize\">\n   <size>\n    <width>800</width>\n    <height>600</height>\n   </size>\n  </property>\n  <property name=\"windowTitle\">\n   <string notr=\"true\">nya</string>\n  </property>\n  <widget class=\"QWidget\" name=\"centralwidget\">\n   <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\" stretch=\"0,0,0\">\n    <item>\n     <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n      <item>\n       <widget class=\"QToolButton\" name=\"toolButton_program\">\n        <property name=\"text\">\n         <string>Program</string>\n        </property>\n        <property name=\"icon\">\n         <iconset theme=\"b-system-run\">\n          <normaloff>.</normaloff>.</iconset>\n        </property>\n        <property name=\"iconSize\">\n         <size>\n          <width>24</width>\n          <height>24</height>\n         </size>\n        </property>\n        <property name=\"popupMode\">\n         <enum>QToolButton::InstantPopup</enum>\n        </property>\n        <property name=\"toolButtonStyle\">\n         <enum>Qt::ToolButtonTextUnderIcon</enum>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QToolButton\" name=\"toolButton_preferences\">\n        <property name=\"text\">\n         <string>Preferences</string>\n        </property>\n        <property name=\"icon\">\n         <iconset theme=\"b-preferences\">\n          <normaloff>.</normaloff>.</iconset>\n        </property>\n        <property name=\"iconSize\">\n         <size>\n          <width>24</width>\n          <height>24</height>\n         </size>\n        </property>\n        <property name=\"popupMode\">\n         <enum>QToolButton::InstantPopup</enum>\n        </property>\n        <property name=\"toolButtonStyle\">\n         <enum>Qt::ToolButtonTextUnderIcon</enum>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QToolButton\" name=\"toolButton_server\">\n        <property name=\"text\">\n         <string>Server</string>\n        </property>\n        <property name=\"icon\">\n         <iconset theme=\"b-network-server\">\n          <normaloff>.</normaloff>.</iconset>\n        </property>\n        <property name=\"iconSize\">\n         <size>\n          <width>24</width>\n          <height>24</height>\n         </size>\n        </property>\n        <property name=\"popupMode\">\n         <enum>QToolButton::InstantPopup</enum>\n        </property>\n        <property name=\"toolButtonStyle\">\n         <enum>Qt::ToolButtonTextUnderIcon</enum>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QToolButton\" name=\"toolButton_ads\">\n        <property name=\"text\">\n         <string>Ads</string>\n        </property>\n        <property name=\"icon\">\n         <iconset theme=\"b-internet-web-browser\">\n          <normaloff>.</normaloff>.</iconset>\n        </property>\n        <property name=\"iconSize\">\n         <size>\n          <width>24</width>\n          <height>24</height>\n         </size>\n        </property>\n        <property name=\"popupMode\">\n         <enum>QToolButton::InstantPopup</enum>\n        </property>\n        <property name=\"toolButtonStyle\">\n         <enum>Qt::ToolButtonTextUnderIcon</enum>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QToolButton\" name=\"toolButton_document\">\n        <property name=\"text\">\n         <string>Document</string>\n        </property>\n        <property name=\"icon\">\n         <iconset theme=\"b-dialog-question\">\n          <normaloff>.</normaloff>.</iconset>\n        </property>\n        <property name=\"iconSize\">\n         <size>\n          <width>24</width>\n          <height>24</height>\n         </size>\n        </property>\n        <property name=\"popupMode\">\n         <enum>QToolButton::InstantPopup</enum>\n        </property>\n        <property name=\"toolButtonStyle\">\n         <enum>Qt::ToolButtonTextUnderIcon</enum>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QToolButton\" name=\"toolButton_update\">\n        <property name=\"text\">\n         <string>Update</string>\n        </property>\n        <property name=\"icon\">\n         <iconset theme=\"b-system-software-update\">\n          <normaloff>.</normaloff>.</iconset>\n        </property>\n        <property name=\"iconSize\">\n         <size>\n          <width>24</width>\n          <height>24</height>\n         </size>\n        </property>\n        <property name=\"popupMode\">\n         <enum>QToolButton::InstantPopup</enum>\n        </property>\n        <property name=\"toolButtonStyle\">\n         <enum>Qt::ToolButtonTextUnderIcon</enum>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <layout class=\"QVBoxLayout\" name=\"verticalLayout_4\">\n        <item>\n         <widget class=\"QCheckBox\" name=\"checkBox_VPN\">\n          <property name=\"sizePolicy\">\n           <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Minimum\">\n            <horstretch>0</horstretch>\n            <verstretch>0</verstretch>\n           </sizepolicy>\n          </property>\n          <property name=\"text\">\n           <string>Tun Mode</string>\n          </property>\n         </widget>\n        </item>\n        <item>\n         <widget class=\"QCheckBox\" name=\"checkBox_SystemProxy\">\n          <property name=\"text\">\n           <string>System Proxy</string>\n          </property>\n         </widget>\n        </item>\n       </layout>\n      </item>\n      <item>\n       <spacer name=\"horizontalSpacer\">\n        <property name=\"orientation\">\n         <enum>Qt::Horizontal</enum>\n        </property>\n        <property name=\"sizeHint\" stdset=\"0\">\n         <size>\n          <width>40</width>\n          <height>20</height>\n         </size>\n        </property>\n       </spacer>\n      </item>\n      <item>\n       <widget class=\"QLineEdit\" name=\"search\">\n        <property name=\"clearButtonEnabled\">\n         <bool>true</bool>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QToolButton\" name=\"toolButton_url_test\">\n        <property name=\"text\">\n         <string>URL Test</string>\n        </property>\n        <property name=\"popupMode\">\n         <enum>QToolButton::InstantPopup</enum>\n        </property>\n        <property name=\"toolButtonStyle\">\n         <enum>Qt::ToolButtonTextUnderIcon</enum>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </item>\n    <item>\n     <widget class=\"QSplitter\" name=\"splitter\">\n      <property name=\"orientation\">\n       <enum>Qt::Vertical</enum>\n      </property>\n      <widget class=\"QTabWidget\" name=\"tabWidget\">\n       <property name=\"currentIndex\">\n        <number>0</number>\n       </property>\n       <property name=\"movable\">\n        <bool>true</bool>\n       </property>\n       <widget class=\"QWidget\" name=\"widget1\">\n        <attribute name=\"title\">\n         <string/>\n        </attribute>\n        <layout class=\"QGridLayout\" name=\"gridLayout_2\">\n         <property name=\"leftMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"topMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"rightMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"bottomMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"spacing\">\n          <number>0</number>\n         </property>\n         <item row=\"0\" column=\"0\">\n          <widget class=\"MyTableWidget\" name=\"proxyListTable\">\n           <property name=\"contextMenuPolicy\">\n            <enum>Qt::CustomContextMenu</enum>\n           </property>\n           <property name=\"editTriggers\">\n            <set>QAbstractItemView::NoEditTriggers</set>\n           </property>\n           <property name=\"alternatingRowColors\">\n            <bool>true</bool>\n           </property>\n           <property name=\"selectionBehavior\">\n            <enum>QAbstractItemView::SelectRows</enum>\n           </property>\n           <property name=\"horizontalScrollMode\">\n            <enum>QAbstractItemView::ScrollPerPixel</enum>\n           </property>\n           <property name=\"wordWrap\">\n            <bool>false</bool>\n           </property>\n           <attribute name=\"horizontalHeaderMinimumSectionSize\">\n            <number>16</number>\n           </attribute>\n           <attribute name=\"horizontalHeaderHighlightSections\">\n            <bool>false</bool>\n           </attribute>\n           <attribute name=\"horizontalHeaderShowSortIndicator\" stdset=\"0\">\n            <bool>false</bool>\n           </attribute>\n           <attribute name=\"verticalHeaderDefaultSectionSize\">\n            <number>30</number>\n           </attribute>\n           <column>\n            <property name=\"text\">\n             <string>Type</string>\n            </property>\n           </column>\n           <column>\n            <property name=\"text\">\n             <string>Address</string>\n            </property>\n           </column>\n           <column>\n            <property name=\"text\">\n             <string>Name</string>\n            </property>\n           </column>\n           <column>\n            <property name=\"text\">\n             <string>Test Result</string>\n            </property>\n           </column>\n           <column>\n            <property name=\"text\">\n             <string>Traffic</string>\n            </property>\n           </column>\n          </widget>\n         </item>\n        </layout>\n       </widget>\n      </widget>\n      <widget class=\"QWidget\" name=\"aaaaaaaaaaaaaaaaaa\">\n       <layout class=\"QVBoxLayout\" name=\"aaaaaaaaaaaa\" stretch=\"0\">\n        <item>\n         <widget class=\"QTabWidget\" name=\"down_tab\">\n          <property name=\"currentIndex\">\n           <number>0</number>\n          </property>\n          <widget class=\"QWidget\" name=\"tab\">\n           <attribute name=\"title\">\n            <string>Log</string>\n           </attribute>\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n            <property name=\"spacing\">\n             <number>0</number>\n            </property>\n            <property name=\"leftMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"topMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"rightMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"bottomMargin\">\n             <number>0</number>\n            </property>\n            <item>\n             <widget class=\"QTextBrowser\" name=\"masterLogBrowser\">\n              <property name=\"contextMenuPolicy\">\n               <enum>Qt::CustomContextMenu</enum>\n              </property>\n              <property name=\"openLinks\">\n               <bool>false</bool>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n          <widget class=\"QWidget\" name=\"tab_2\">\n           <attribute name=\"title\">\n            <string>Connection</string>\n           </attribute>\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n            <property name=\"spacing\">\n             <number>0</number>\n            </property>\n            <property name=\"leftMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"topMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"rightMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"bottomMargin\">\n             <number>0</number>\n            </property>\n            <item>\n             <widget class=\"QTableWidget\" name=\"tableWidget_conn\">\n              <property name=\"contextMenuPolicy\">\n               <enum>Qt::CustomContextMenu</enum>\n              </property>\n              <property name=\"editTriggers\">\n               <set>QAbstractItemView::NoEditTriggers</set>\n              </property>\n              <property name=\"selectionBehavior\">\n               <enum>QAbstractItemView::SelectRows</enum>\n              </property>\n              <property name=\"verticalScrollMode\">\n               <enum>QAbstractItemView::ScrollPerPixel</enum>\n              </property>\n              <property name=\"horizontalScrollMode\">\n               <enum>QAbstractItemView::ScrollPerPixel</enum>\n              </property>\n              <attribute name=\"horizontalHeaderHighlightSections\">\n               <bool>false</bool>\n              </attribute>\n              <column>\n               <property name=\"text\">\n                <string>Status</string>\n               </property>\n              </column>\n              <column>\n               <property name=\"text\">\n                <string>Outbound</string>\n               </property>\n              </column>\n              <column>\n               <property name=\"text\">\n                <string>Destination</string>\n               </property>\n              </column>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </widget>\n        </item>\n       </layout>\n      </widget>\n     </widget>\n    </item>\n    <item>\n     <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n      <item>\n       <widget class=\"QLabel\" name=\"label_running\">\n        <property name=\"text\">\n         <string/>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <spacer name=\"horizontalSpacer_2\">\n        <property name=\"orientation\">\n         <enum>Qt::Horizontal</enum>\n        </property>\n        <property name=\"sizeType\">\n         <enum>QSizePolicy::Maximum</enum>\n        </property>\n        <property name=\"sizeHint\" stdset=\"0\">\n         <size>\n          <width>0</width>\n          <height>20</height>\n         </size>\n        </property>\n       </spacer>\n      </item>\n      <item>\n       <widget class=\"QLabel\" name=\"label_inbound\">\n        <property name=\"text\">\n         <string/>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QLabel\" name=\"label_speed\">\n        <property name=\"text\">\n         <string/>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </item>\n   </layout>\n  </widget>\n  <widget class=\"QMenuBar\" name=\"menubar\">\n   <property name=\"geometry\">\n    <rect>\n     <x>0</x>\n     <y>0</y>\n     <width>800</width>\n     <height>32</height>\n    </rect>\n   </property>\n   <widget class=\"QMenu\" name=\"menu_program\">\n    <property name=\"title\">\n     <string>Program</string>\n    </property>\n    <widget class=\"QMenu\" name=\"menu_spmode\">\n     <property name=\"title\">\n      <string>System Proxy</string>\n     </property>\n     <addaction name=\"menu_spmode_system_proxy\"/>\n     <addaction name=\"menu_spmode_vpn\"/>\n     <addaction name=\"menu_spmode_disabled\"/>\n    </widget>\n    <widget class=\"QMenu\" name=\"menu_program_preference\">\n     <property name=\"title\">\n      <string>Preferences</string>\n     </property>\n     <addaction name=\"actionfake\"/>\n    </widget>\n    <widget class=\"QMenu\" name=\"menuActive_Server\">\n     <property name=\"title\">\n      <string>Active Server</string>\n     </property>\n     <addaction name=\"actionfake_2\"/>\n    </widget>\n    <widget class=\"QMenu\" name=\"menuActive_Routing\">\n     <property name=\"title\">\n      <string>Active Routing</string>\n     </property>\n     <addaction name=\"actionfake_3\"/>\n    </widget>\n    <addaction name=\"actionShow_window\"/>\n    <addaction name=\"menu_add_from_clipboard2\"/>\n    <addaction name=\"menu_scan_qr\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionStart_with_system\"/>\n    <addaction name=\"actionRemember_last_proxy\"/>\n    <addaction name=\"actionAllow_LAN\"/>\n    <addaction name=\"menuActive_Server\"/>\n    <addaction name=\"menuActive_Routing\"/>\n    <addaction name=\"menu_spmode\"/>\n    <addaction name=\"menu_program_preference\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionRestart_Proxy\"/>\n    <addaction name=\"actionRestart_Program\"/>\n    <addaction name=\"menu_exit\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menu_preferences\">\n    <property name=\"title\">\n     <string>Preferences</string>\n    </property>\n    <addaction name=\"menu_manage_groups\"/>\n    <addaction name=\"menu_basic_settings\"/>\n    <addaction name=\"menu_routing_settings\"/>\n    <addaction name=\"menu_vpn_settings\"/>\n    <addaction name=\"menu_hotkey_settings\"/>\n    <addaction name=\"menu_open_config_folder\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menu_server\">\n    <property name=\"title\">\n     <string>Server</string>\n    </property>\n    <widget class=\"QMenu\" name=\"menu_share_item\">\n     <property name=\"title\">\n      <string>Share</string>\n     </property>\n     <addaction name=\"menu_qr\"/>\n     <addaction name=\"menu_export_config\"/>\n     <addaction name=\"separator\"/>\n     <addaction name=\"menu_copy_links\"/>\n     <addaction name=\"menu_copy_links_nkr\"/>\n    </widget>\n    <widget class=\"QMenu\" name=\"menuCurrent_Group\">\n     <property name=\"title\">\n      <string>Current Group</string>\n     </property>\n     <addaction name=\"actionfake_5\"/>\n     <addaction name=\"menu_tcp_ping\"/>\n     <addaction name=\"menu_url_test\"/>\n     <addaction name=\"menu_full_test\"/>\n     <addaction name=\"menu_stop_testing\"/>\n     <addaction name=\"menu_clear_test_result\"/>\n     <addaction name=\"menu_resolve_domain\"/>\n     <addaction name=\"separator\"/>\n     <addaction name=\"menu_remove_unavailable\"/>\n     <addaction name=\"menu_delete_repeat\"/>\n     <addaction name=\"separator\"/>\n     <addaction name=\"menu_update_subscription\"/>\n    </widget>\n    <widget class=\"QMenu\" name=\"menuCurrent_Select\">\n     <property name=\"title\">\n      <string>Current Select</string>\n     </property>\n     <addaction name=\"actionfake_4\"/>\n    </widget>\n    <addaction name=\"menu_add_from_input\"/>\n    <addaction name=\"menu_add_from_clipboard\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"menu_start\"/>\n    <addaction name=\"menu_stop\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"menu_select_all\"/>\n    <addaction name=\"menu_move\"/>\n    <addaction name=\"menu_clone\"/>\n    <addaction name=\"menu_reset_traffic\"/>\n    <addaction name=\"menu_delete\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"menu_share_item\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"menuCurrent_Select\"/>\n    <addaction name=\"menuCurrent_Group\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"menu_profile_debug_info\"/>\n    <addaction name=\"separator\"/>\n   </widget>\n   <addaction name=\"menu_program\"/>\n   <addaction name=\"menu_preferences\"/>\n   <addaction name=\"menu_server\"/>\n  </widget>\n  <action name=\"menu_exit\">\n   <property name=\"text\">\n    <string>Exit</string>\n   </property>\n  </action>\n  <action name=\"actionShow_window\">\n   <property name=\"text\">\n    <string>Show Window</string>\n   </property>\n  </action>\n  <action name=\"menu_basic_settings\">\n   <property name=\"text\">\n    <string>Basic Settings</string>\n   </property>\n  </action>\n  <action name=\"menu_add_from_input\">\n   <property name=\"text\">\n    <string>New profile</string>\n   </property>\n  </action>\n  <action name=\"menu_manage_groups\">\n   <property name=\"text\">\n    <string>Groups</string>\n   </property>\n  </action>\n  <action name=\"menu_start\">\n   <property name=\"text\">\n    <string>Start</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Return</string>\n   </property>\n  </action>\n  <action name=\"menu_stop\">\n   <property name=\"text\">\n    <string>Stop</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+S</string>\n   </property>\n  </action>\n  <action name=\"menu_routing_settings\">\n   <property name=\"text\">\n    <string>Routing Settings</string>\n   </property>\n  </action>\n  <action name=\"menu_add_from_clipboard\">\n   <property name=\"text\">\n    <string>Add profile from clipboard</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+V</string>\n   </property>\n  </action>\n  <action name=\"menu_delete\">\n   <property name=\"text\">\n    <string>Delete</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Del</string>\n   </property>\n  </action>\n  <action name=\"menu_add_from_clipboard2\">\n   <property name=\"text\">\n    <string>Add profile from clipboard</string>\n   </property>\n  </action>\n  <action name=\"menu_profile_debug_info\">\n   <property name=\"text\">\n    <string>Debug Info</string>\n   </property>\n  </action>\n  <action name=\"menu_qr\">\n   <property name=\"text\">\n    <string>QR Code and link</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+Q</string>\n   </property>\n  </action>\n  <action name=\"menu_copy_link\">\n   <property name=\"text\">\n    <string>Copy Link</string>\n   </property>\n  </action>\n  <action name=\"menu_tcp_ping\">\n   <property name=\"text\">\n    <string notr=\"true\">Tcp Ping</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+Alt+T</string>\n   </property>\n  </action>\n  <action name=\"menu_url_test\">\n   <property name=\"text\">\n    <string notr=\"true\">Url Test</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+Alt+U</string>\n   </property>\n  </action>\n  <action name=\"menu_clear_test_result\">\n   <property name=\"text\">\n    <string>Clear Test Result</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+Alt+C</string>\n   </property>\n  </action>\n  <action name=\"menu_export_config\">\n   <property name=\"text\">\n    <string>Export %1 config</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+E</string>\n   </property>\n  </action>\n  <action name=\"menu_reset_traffic\">\n   <property name=\"text\">\n    <string>Reset Traffic</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+R</string>\n   </property>\n  </action>\n  <action name=\"menu_scan_qr\">\n   <property name=\"text\">\n    <string>Scan QR Code</string>\n   </property>\n  </action>\n  <action name=\"menu_spmode_system_proxy\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>Enable System Proxy</string>\n   </property>\n  </action>\n  <action name=\"menu_spmode_disabled\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>Disable</string>\n   </property>\n  </action>\n  <action name=\"menu_delete_repeat\">\n   <property name=\"text\">\n    <string>Remove Duplicates</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+Alt+D</string>\n   </property>\n  </action>\n  <action name=\"actionfake\">\n   <property name=\"text\">\n    <string>fake</string>\n   </property>\n   <property name=\"visible\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"menu_move\">\n   <property name=\"text\">\n    <string>Move</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+M</string>\n   </property>\n  </action>\n  <action name=\"actionStart_with_system\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>Start with system</string>\n   </property>\n  </action>\n  <action name=\"actionRemember_last_proxy\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>Remember last profile</string>\n   </property>\n  </action>\n  <action name=\"actionAllow_LAN\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>Allow other devices to connect</string>\n   </property>\n  </action>\n  <action name=\"menu_remove_unavailable\">\n   <property name=\"text\">\n    <string>Remove Unavailable</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+Alt+R</string>\n   </property>\n  </action>\n  <action name=\"menu_full_test\">\n   <property name=\"text\">\n    <string>Full Test</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+Alt+F</string>\n   </property>\n  </action>\n  <action name=\"menu_hotkey_settings\">\n   <property name=\"text\">\n    <string>Hotkey Settings</string>\n   </property>\n  </action>\n  <action name=\"menu_select_all\">\n   <property name=\"text\">\n    <string>Select All</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+A</string>\n   </property>\n  </action>\n  <action name=\"menu_copy_links_nkr\">\n   <property name=\"text\">\n    <string>Copy links of selected (Neko Links)</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+N</string>\n   </property>\n  </action>\n  <action name=\"actionfake_2\">\n   <property name=\"text\">\n    <string>fake</string>\n   </property>\n   <property name=\"visible\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionfake_3\">\n   <property name=\"text\">\n    <string>fake</string>\n   </property>\n   <property name=\"visible\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"menu_copy_links\">\n   <property name=\"text\">\n    <string>Copy links of selected</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+C</string>\n   </property>\n  </action>\n  <action name=\"menu_spmode_vpn\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>Enable Tun</string>\n   </property>\n  </action>\n  <action name=\"menu_clone\">\n   <property name=\"text\">\n    <string>Clone</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+D</string>\n   </property>\n  </action>\n  <action name=\"menu_update_subscription\">\n   <property name=\"text\">\n    <string>Update subscription</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+U</string>\n   </property>\n  </action>\n  <action name=\"menu_resolve_domain\">\n   <property name=\"text\">\n    <string>Resolve domain</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+Alt+I</string>\n   </property>\n  </action>\n  <action name=\"menu_vpn_settings\">\n   <property name=\"text\">\n    <string>Tun Settings</string>\n   </property>\n  </action>\n  <action name=\"actionRestart_Program\">\n   <property name=\"text\">\n    <string>Restart Program</string>\n   </property>\n  </action>\n  <action name=\"menu_open_config_folder\">\n   <property name=\"text\">\n    <string>Open Config Folder</string>\n   </property>\n  </action>\n  <action name=\"actionfake_4\">\n   <property name=\"text\">\n    <string notr=\"true\">fake</string>\n   </property>\n   <property name=\"visible\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionfake_5\">\n   <property name=\"text\">\n    <string notr=\"true\">fake</string>\n   </property>\n   <property name=\"visible\">\n    <bool>false</bool>\n   </property>\n  </action>\n  <action name=\"actionRestart_Proxy\">\n   <property name=\"text\">\n    <string>Restart Proxy</string>\n   </property>\n  </action>\n  <action name=\"menu_stop_testing\">\n   <property name=\"text\">\n    <string>Stop Testing</string>\n   </property>\n   <property name=\"shortcut\">\n    <string notr=\"true\">Ctrl+Alt+S</string>\n   </property>\n  </action>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>MyTableWidget</class>\n   <extends>QTableWidget</extends>\n   <header>ui/widget/MyTableWidget.h</header>\n  </customwidget>\n </customwidgets>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "ui/mainwindow_grpc.cpp",
    "content": "#include \"./ui_mainwindow.h\"\n#include \"mainwindow.h\"\n\n#include \"db/Database.hpp\"\n#include \"db/ConfigBuilder.hpp\"\n#include \"db/traffic/TrafficLooper.hpp\"\n#include \"rpc/gRPC.h\"\n#include \"ui/widget/MessageBoxTimer.h\"\n\n#include <QTimer>\n#include <QThread>\n#include <QInputDialog>\n#include <QPushButton>\n#include <QDesktopServices>\n#include <QMessageBox>\n#include <QDialogButtonBox>\n\n// ext core\n\nstd::list<std::shared_ptr<NekoGui_sys::ExternalProcess>> CreateExtCFromExtR(const std::list<std::shared_ptr<NekoGui_fmt::ExternalBuildResult>> &extRs, bool start) {\n    // plz run and start in same thread\n    std::list<std::shared_ptr<NekoGui_sys::ExternalProcess>> l;\n    for (const auto &extR: extRs) {\n        std::shared_ptr<NekoGui_sys::ExternalProcess> extC(new NekoGui_sys::ExternalProcess());\n        extC->tag = extR->tag;\n        extC->program = extR->program;\n        extC->arguments = extR->arguments;\n        extC->env = extR->env;\n        l.emplace_back(extC);\n        //\n        if (start) extC->Start();\n    }\n    return l;\n}\n\n// grpc\n\n#ifndef NKR_NO_GRPC\nusing namespace NekoGui_rpc;\n#endif\n\nvoid MainWindow::setup_grpc() {\n#ifndef NKR_NO_GRPC\n    // Setup Connection\n    defaultClient = new Client(\n        [=](const QString &errStr) {\n            MW_show_log(\"[Error] gRPC: \" + errStr);\n        },\n        \"127.0.0.1:\" + Int2String(NekoGui::dataStore->core_port), NekoGui::dataStore->core_token);\n\n    // Looper\n    runOnNewThread([=] { NekoGui_traffic::trafficLooper->Loop(); });\n#endif\n}\n\n// 测速\n\ninline bool speedtesting = false;\ninline QList<QThread *> speedtesting_threads = {};\n\nvoid MainWindow::speedtest_current_group(int mode, bool test_group) {\n    if (speedtesting) {\n        MessageBoxWarning(software_name, QObject::tr(\"The last speed test did not exit completely, please wait. If it persists, please restart the program.\"));\n        return;\n    }\n\n    auto profiles = get_selected_or_group();\n    if (test_group) profiles = NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder();\n    if (profiles.isEmpty()) return;\n    auto group = NekoGui::profileManager->CurrentGroup();\n    if (group->archive) return;\n\n    // menu_stop_testing\n    if (mode == 114514) {\n        while (!speedtesting_threads.isEmpty()) {\n            auto t = speedtesting_threads.takeFirst();\n            if (t != nullptr) t->exit();\n        }\n        speedtesting = false;\n        return;\n    }\n\n#ifndef NKR_NO_GRPC\n    QStringList full_test_flags;\n    if (mode == libcore::FullTest) {\n        auto w = new QDialog(this);\n        auto layout = new QVBoxLayout(w);\n        w->setWindowTitle(tr(\"Test Options\"));\n        //\n        auto l1 = new QCheckBox(tr(\"Latency\"));\n        auto l2 = new QCheckBox(tr(\"UDP latency\"));\n        auto l3 = new QCheckBox(tr(\"Download speed\"));\n        auto l4 = new QCheckBox(tr(\"In and Out IP\"));\n        //\n        auto box = new QDialogButtonBox;\n        box->setOrientation(Qt::Horizontal);\n        box->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);\n        connect(box, &QDialogButtonBox::accepted, w, &QDialog::accept);\n        connect(box, &QDialogButtonBox::rejected, w, &QDialog::reject);\n        //\n        layout->addWidget(l1);\n        layout->addWidget(l2);\n        layout->addWidget(l3);\n        layout->addWidget(l4);\n        layout->addWidget(box);\n        if (w->exec() != QDialog::Accepted) {\n            w->deleteLater();\n            return;\n        }\n        //\n        if (l1->isChecked()) full_test_flags << \"1\";\n        if (l2->isChecked()) full_test_flags << \"2\";\n        if (l3->isChecked()) full_test_flags << \"3\";\n        if (l4->isChecked()) full_test_flags << \"4\";\n        //\n        w->deleteLater();\n        if (full_test_flags.isEmpty()) return;\n    }\n    speedtesting = true;\n\n    runOnNewThread([this, profiles, mode, full_test_flags]() {\n        QMutex lock_write;\n        QMutex lock_return;\n        int threadN = NekoGui::dataStore->test_concurrent;\n        int threadN_finished = 0;\n        auto profiles_test = profiles; // copy\n\n        // Threads\n        lock_return.lock();\n        for (int i = 0; i < threadN; i++) {\n            runOnNewThread([&] {\n                speedtesting_threads << QObject::thread();\n\n                forever {\n                    //\n                    lock_write.lock();\n                    if (profiles_test.isEmpty()) {\n                        threadN_finished++;\n                        if (threadN == threadN_finished) {\n                            // quit control thread\n                            lock_return.unlock();\n                        }\n                        lock_write.unlock();\n                        // quit of this thread\n                        speedtesting_threads.removeAll(QObject::thread());\n                        return;\n                    }\n                    auto profile = profiles_test.takeFirst();\n                    lock_write.unlock();\n\n                    //\n                    libcore::TestReq req;\n                    req.set_mode((libcore::TestMode) mode);\n                    req.set_timeout(10 * 1000);\n                    req.set_url(NekoGui::dataStore->test_latency_url.toStdString());\n\n                    //\n                    std::list<std::shared_ptr<NekoGui_sys::ExternalProcess>> extCs;\n                    QSemaphore extSem;\n\n                    if (mode == libcore::TestMode::UrlTest || mode == libcore::FullTest) {\n                        auto c = BuildConfig(profile, true, false);\n                        if (!c->error.isEmpty()) {\n                            profile->full_test_report = c->error;\n                            profile->Save();\n                            auto profileId = profile->id;\n                            runOnUiThread([this, profileId] {\n                                refresh_proxy_list(profileId);\n                            });\n                            continue;\n                        }\n                        //\n                        if (!c->extRs.empty()) {\n                            runOnUiThread(\n                                [&] {\n                                    extCs = CreateExtCFromExtR(c->extRs, true);\n                                    QThread::msleep(500);\n                                    extSem.release();\n                                },\n                                DS_cores);\n                            extSem.acquire();\n                        }\n                        //\n                        auto config = new libcore::LoadConfigReq;\n                        config->set_core_config(QJsonObject2QString(c->coreConfig, false).toStdString());\n                        req.set_allocated_config(config);\n                        req.set_in_address(profile->bean->serverAddress.toStdString());\n\n                        req.set_full_latency(full_test_flags.contains(\"1\"));\n                        req.set_full_udp_latency(full_test_flags.contains(\"2\"));\n                        req.set_full_speed(full_test_flags.contains(\"3\"));\n                        req.set_full_in_out(full_test_flags.contains(\"4\"));\n\n                        req.set_full_speed_url(NekoGui::dataStore->test_download_url.toStdString());\n                        req.set_full_speed_timeout(NekoGui::dataStore->test_download_timeout);\n                    } else if (mode == libcore::TcpPing) {\n                        req.set_address(profile->bean->DisplayAddress().toStdString());\n                    }\n\n                    bool rpcOK;\n                    auto result = defaultClient->Test(&rpcOK, req);\n                    //\n                    if (!extCs.empty()) {\n                        runOnUiThread(\n                            [&] {\n                                for (const auto &extC: extCs) {\n                                    extC->Kill();\n                                }\n                                extSem.release();\n                            },\n                            DS_cores);\n                        extSem.acquire();\n                    }\n                    //\n                    if (!rpcOK) return;\n\n                    if (result.error().empty()) {\n                        profile->latency = result.ms();\n                        if (profile->latency == 0) profile->latency = 1; // nekoray use 0 to represents not tested\n                    } else {\n                        profile->latency = -1;\n                    }\n                    profile->full_test_report = result.full_report().c_str(); // higher priority\n                    profile->Save();\n\n                    if (!result.error().empty()) {\n                        MW_show_log(tr(\"[%1] test error: %2\").arg(profile->bean->DisplayTypeAndName(), result.error().c_str()));\n                    }\n\n                    auto profileId = profile->id;\n                    runOnUiThread([this, profileId] {\n                        refresh_proxy_list(profileId);\n                    });\n                }\n            });\n        }\n\n        // Control\n        lock_return.lock();\n        lock_return.unlock();\n        speedtesting = false;\n        MW_show_log(QObject::tr(\"Speedtest finished.\"));\n    });\n#endif\n}\n\nvoid MainWindow::speedtest_current() {\n#ifndef NKR_NO_GRPC\n    last_test_time = QTime::currentTime();\n    ui->label_running->setText(tr(\"Testing\"));\n\n    runOnNewThread([=] {\n        libcore::TestReq req;\n        req.set_mode(libcore::UrlTest);\n        req.set_timeout(10 * 1000);\n        req.set_url(NekoGui::dataStore->test_latency_url.toStdString());\n\n        bool rpcOK;\n        auto result = defaultClient->Test(&rpcOK, req);\n        if (!rpcOK) return;\n\n        auto latency = result.ms();\n        last_test_time = QTime::currentTime();\n\n        runOnUiThread([=] {\n            if (!result.error().empty()) {\n                MW_show_log(QStringLiteral(\"UrlTest error: %1\").arg(result.error().c_str()));\n            }\n            if (latency <= 0) {\n                ui->label_running->setText(tr(\"Test Result\") + \": \" + tr(\"Unavailable\"));\n            } else if (latency > 0) {\n                ui->label_running->setText(tr(\"Test Result\") + \": \" + QStringLiteral(\"%1 ms\").arg(latency));\n            }\n        });\n    });\n#endif\n}\n\nvoid MainWindow::stop_core_daemon() {\n#ifndef NKR_NO_GRPC\n    NekoGui_rpc::defaultClient->Exit();\n#endif\n}\n\nvoid MainWindow::neko_start(int _id) {\n    if (NekoGui::dataStore->prepare_exit) return;\n\n    auto ents = get_now_selected_list();\n    auto ent = (_id < 0 && !ents.isEmpty()) ? ents.first() : NekoGui::profileManager->GetProfile(_id);\n    if (ent == nullptr) return;\n\n    if (select_mode) {\n        emit profile_selected(ent->id);\n        select_mode = false;\n        refresh_status();\n        return;\n    }\n\n    auto group = NekoGui::profileManager->GetGroup(ent->gid);\n    if (group == nullptr || group->archive) return;\n\n    auto result = BuildConfig(ent, false, false);\n    if (!result->error.isEmpty()) {\n        MessageBoxWarning(\"BuildConfig return error\", result->error);\n        return;\n    }\n\n    auto neko_start_stage2 = [=] {\n#ifndef NKR_NO_GRPC\n        libcore::LoadConfigReq req;\n        req.set_core_config(QJsonObject2QString(result->coreConfig, false).toStdString());\n        req.set_enable_nekoray_connections(NekoGui::dataStore->connection_statistics);\n        if (NekoGui::dataStore->traffic_loop_interval > 0) {\n            req.add_stats_outbounds(\"proxy\");\n            req.add_stats_outbounds(\"bypass\");\n        }\n        //\n        bool rpcOK;\n        QString error = defaultClient->Start(&rpcOK, req);\n        if (rpcOK && !error.isEmpty()) {\n            runOnUiThread([=] { MessageBoxWarning(\"LoadConfig return error\", error); });\n            return false;\n        } else if (!rpcOK) {\n            return false;\n        }\n        //\n        NekoGui_traffic::trafficLooper->proxy = result->outboundStat.get();\n        NekoGui_traffic::trafficLooper->items = result->outboundStats;\n        NekoGui::dataStore->ignoreConnTag = result->ignoreConnTag;\n        NekoGui_traffic::trafficLooper->loop_enabled = true;\n#endif\n\n        runOnUiThread(\n            [=] {\n                auto extCs = CreateExtCFromExtR(result->extRs, true);\n                NekoGui_sys::running_ext.splice(NekoGui_sys::running_ext.end(), extCs);\n            },\n            DS_cores);\n\n        NekoGui::dataStore->UpdateStartedId(ent->id);\n        running = ent;\n\n        runOnUiThread([=] {\n            refresh_status();\n            refresh_proxy_list(ent->id);\n        });\n\n        return true;\n    };\n\n    if (!mu_starting.tryLock()) {\n        MessageBoxWarning(software_name, \"Another profile is starting...\");\n        return;\n    }\n    if (!mu_stopping.tryLock()) {\n        MessageBoxWarning(software_name, \"Another profile is stopping...\");\n        mu_starting.unlock();\n        return;\n    }\n    mu_stopping.unlock();\n\n    // check core state\n    if (!NekoGui::dataStore->core_running) {\n        runOnUiThread(\n            [=] {\n                MW_show_log(\"Try to start the config, but the core has not listened to the grpc port, so restart it...\");\n                core_process->start_profile_when_core_is_up = ent->id;\n                core_process->Restart();\n            },\n            DS_cores);\n        mu_starting.unlock();\n        return; // let CoreProcess call neko_start when core is up\n    }\n\n    // timeout message\n    auto restartMsgbox = new QMessageBox(QMessageBox::Question, software_name, tr(\"If there is no response for a long time, it is recommended to restart the software.\"),\n                                         QMessageBox::Yes | QMessageBox::No, this);\n    connect(restartMsgbox, &QMessageBox::accepted, this, [=] { MW_dialog_message(\"\", \"RestartProgram\"); });\n    auto restartMsgboxTimer = new MessageBoxTimer(this, restartMsgbox, 5000);\n\n    runOnNewThread([=] {\n        // stop current running\n        if (NekoGui::dataStore->started_id >= 0) {\n            runOnUiThread([=] { neko_stop(false, true); });\n            sem_stopped.acquire();\n        }\n        // do start\n        MW_show_log(\">>>>>>>> \" + tr(\"Starting profile %1\").arg(ent->bean->DisplayTypeAndName()));\n        if (!neko_start_stage2()) {\n            MW_show_log(\"<<<<<<<< \" + tr(\"Failed to start profile %1\").arg(ent->bean->DisplayTypeAndName()));\n        }\n        mu_starting.unlock();\n        // cancel timeout\n        runOnUiThread([=] {\n            restartMsgboxTimer->cancel();\n            restartMsgboxTimer->deleteLater();\n            restartMsgbox->deleteLater();\n#ifdef Q_OS_LINUX\n            // Check systemd-resolved\n            if (NekoGui::dataStore->spmode_vpn && NekoGui::dataStore->routing->direct_dns.startsWith(\"local\") && ReadFileText(\"/etc/resolv.conf\").contains(\"systemd-resolved\")) {\n                MW_show_log(\"[Warning] The default Direct DNS may not works with systemd-resolved, you may consider change your DNS settings.\");\n            }\n#endif\n        });\n    });\n}\n\nvoid MainWindow::neko_stop(bool crash, bool sem) {\n    auto id = NekoGui::dataStore->started_id;\n    if (id < 0) {\n        if (sem) sem_stopped.release();\n        return;\n    }\n\n    auto neko_stop_stage2 = [=] {\n        runOnUiThread(\n            [=] {\n                while (!NekoGui_sys::running_ext.empty()) {\n                    auto extC = NekoGui_sys::running_ext.front();\n                    extC->Kill();\n                    NekoGui_sys::running_ext.pop_front();\n                }\n            },\n            DS_cores);\n\n#ifndef NKR_NO_GRPC\n        NekoGui_traffic::trafficLooper->loop_enabled = false;\n        NekoGui_traffic::trafficLooper->loop_mutex.lock();\n        if (NekoGui::dataStore->traffic_loop_interval != 0) {\n            NekoGui_traffic::trafficLooper->UpdateAll();\n            for (const auto &item: NekoGui_traffic::trafficLooper->items) {\n                NekoGui::profileManager->GetProfile(item->id)->Save();\n                runOnUiThread([=] { refresh_proxy_list(item->id); });\n            }\n        }\n        NekoGui_traffic::trafficLooper->loop_mutex.unlock();\n\n        if (!crash) {\n            bool rpcOK;\n            QString error = defaultClient->Stop(&rpcOK);\n            if (rpcOK && !error.isEmpty()) {\n                runOnUiThread([=] { MessageBoxWarning(\"Stop return error\", error); });\n                return false;\n            } else if (!rpcOK) {\n                return false;\n            }\n        }\n#endif\n\n        NekoGui::dataStore->UpdateStartedId(-1919);\n        NekoGui::dataStore->need_keep_vpn_off = false;\n        running = nullptr;\n\n        runOnUiThread([=] {\n            refresh_status();\n            refresh_proxy_list(id);\n        });\n\n        return true;\n    };\n\n    if (!mu_stopping.tryLock()) {\n        if (sem) sem_stopped.release();\n        return;\n    }\n\n    // timeout message\n    auto restartMsgbox = new QMessageBox(QMessageBox::Question, software_name, tr(\"If there is no response for a long time, it is recommended to restart the software.\"),\n                                         QMessageBox::Yes | QMessageBox::No, this);\n    connect(restartMsgbox, &QMessageBox::accepted, this, [=] { MW_dialog_message(\"\", \"RestartProgram\"); });\n    auto restartMsgboxTimer = new MessageBoxTimer(this, restartMsgbox, 5000);\n\n    runOnNewThread([=] {\n        // do stop\n        MW_show_log(\">>>>>>>> \" + tr(\"Stopping profile %1\").arg(running->bean->DisplayTypeAndName()));\n        if (!neko_stop_stage2()) {\n            MW_show_log(\"<<<<<<<< \" + tr(\"Failed to stop, please restart the program.\"));\n        }\n        mu_stopping.unlock();\n        if (sem) sem_stopped.release();\n        // cancel timeout\n        runOnUiThread([=] {\n            restartMsgboxTimer->cancel();\n            restartMsgboxTimer->deleteLater();\n            restartMsgbox->deleteLater();\n        });\n    });\n}\n\nvoid MainWindow::CheckUpdate() {\n    // on new thread...\n#ifndef NKR_NO_GRPC\n    bool ok;\n    libcore::UpdateReq request;\n    request.set_action(libcore::UpdateAction::Check);\n    request.set_check_pre_release(NekoGui::dataStore->check_include_pre);\n    auto response = NekoGui_rpc::defaultClient->Update(&ok, request);\n    if (!ok) return;\n\n    auto err = response.error();\n    if (!err.empty()) {\n        runOnUiThread([=] {\n            MessageBoxWarning(QObject::tr(\"Update\"), err.c_str());\n        });\n        return;\n    }\n\n    if (response.release_download_url() == nullptr) {\n        runOnUiThread([=] {\n            MessageBoxInfo(QObject::tr(\"Update\"), QObject::tr(\"No update\"));\n        });\n        return;\n    }\n\n    runOnUiThread([=] {\n        auto allow_updater = !NekoGui::dataStore->flag_use_appdata;\n        auto note_pre_release = response.is_pre_release() ? \" (Pre-release)\" : \"\";\n        QMessageBox box(QMessageBox::Question, QObject::tr(\"Update\") + note_pre_release,\n                        QObject::tr(\"Update found: %1\\nRelease note:\\n%2\").arg(response.assets_name().c_str(), response.release_note().c_str()));\n        //\n        QAbstractButton *btn1 = nullptr;\n        if (allow_updater) {\n            btn1 = box.addButton(QObject::tr(\"Update\"), QMessageBox::AcceptRole);\n        }\n        QAbstractButton *btn2 = box.addButton(QObject::tr(\"Open in browser\"), QMessageBox::AcceptRole);\n        box.addButton(QObject::tr(\"Close\"), QMessageBox::RejectRole);\n        box.exec();\n        //\n        if (btn1 == box.clickedButton() && allow_updater) {\n            // Download Update\n            runOnNewThread([=] {\n                bool ok2;\n                libcore::UpdateReq request2;\n                request2.set_action(libcore::UpdateAction::Download);\n                auto response2 = NekoGui_rpc::defaultClient->Update(&ok2, request2);\n                runOnUiThread([=] {\n                    if (response2.error().empty()) {\n                        auto q = QMessageBox::question(nullptr, QObject::tr(\"Update\"),\n                                                       QObject::tr(\"Update is ready, restart to install?\"));\n                        if (q == QMessageBox::StandardButton::Yes) {\n                            this->exit_reason = 1;\n                            on_menu_exit_triggered();\n                        }\n                    } else {\n                        MessageBoxWarning(QObject::tr(\"Update\"), response2.error().c_str());\n                    }\n                });\n            });\n        } else if (btn2 == box.clickedButton()) {\n            QDesktopServices::openUrl(QUrl(response.release_url().c_str()));\n        }\n    });\n#endif\n}\n"
  },
  {
    "path": "ui/mainwindow_interface.h",
    "content": "#pragma once\n\n#define MW_INTERFACE\n\n#include \"mainwindow.h\"\n"
  },
  {
    "path": "ui/widget/FloatCheckBox.h",
    "content": "#pragma once\n\n#include <QEvent>\n#include <QCheckBox>\n\nclass FloatCheckBox : public QCheckBox {\npublic:\n    QWidget *parent;\n    QWidget *window;\n\n    void refresh() {\n        setFixedSize(24, 24);\n        auto pos = parent->rect().topRight();\n        pos = parent->mapTo(window, pos);\n        pos.setX(pos.x() - 48); // ?\n        move(pos);\n        raise();\n        setVisible(parent->isVisible());\n    };\n\n    bool eventFilter(QObject *obj, QEvent *e) override {\n        if (obj != window || e->type() != QEvent::Resize) return false;\n        refresh();\n        return false;\n    };\n\n    explicit FloatCheckBox(QWidget *parent, QWidget *window) : QCheckBox(window) {\n        this->parent = parent;\n        this->window = window;\n        window->installEventFilter(this);\n        refresh();\n    };\n};\n"
  },
  {
    "path": "ui/widget/GroupItem.cpp",
    "content": "#include \"GroupItem.h\"\n#include \"ui_GroupItem.h\"\n\n#include \"ui/edit/dialog_edit_group.h\"\n#include \"main/GuiUtils.hpp\"\n#include \"sub/GroupUpdater.hpp\"\n\n#include <QMessageBox>\n\nQString ParseSubInfo(const QString &info) {\n    if (info.trimmed().isEmpty()) return \"\";\n\n    QString result;\n\n    long long used = 0;\n    long long total = 0;\n    long long expire = 0;\n\n    auto re0m = QRegularExpression(\"total=([0-9]+)\").match(info);\n    if (re0m.lastCapturedIndex() >= 1) {\n        total = re0m.captured(1).toLongLong();\n    } else {\n        return \"\";\n    }\n    auto re1m = QRegularExpression(\"upload=([0-9]+)\").match(info);\n    if (re1m.lastCapturedIndex() >= 1) {\n        used += re1m.captured(1).toLongLong();\n    }\n    auto re2m = QRegularExpression(\"download=([0-9]+)\").match(info);\n    if (re2m.lastCapturedIndex() >= 1) {\n        used += re2m.captured(1).toLongLong();\n    }\n    auto re3m = QRegularExpression(\"expire=([0-9]+)\").match(info);\n    if (re3m.lastCapturedIndex() >= 1) {\n        expire = re3m.captured(1).toLongLong();\n    }\n\n    result = QObject::tr(\"Used: %1 Remain: %2 Expire: %3\")\n                 .arg(ReadableSize(used), ReadableSize(total - used), DisplayTime(expire, QLocale::ShortFormat));\n\n    return result;\n}\n\nGroupItem::GroupItem(QWidget *parent, const std::shared_ptr<NekoGui::Group> &ent, QListWidgetItem *item) : QWidget(parent), ui(new Ui::GroupItem) {\n    ui->setupUi(this);\n    this->setLayoutDirection(Qt::LeftToRight);\n\n    this->parentWindow = parent;\n    this->ent = ent;\n    this->item = item;\n    if (ent == nullptr) return;\n\n    connect(this, &GroupItem::edit_clicked, this, &GroupItem::on_edit_clicked);\n    connect(NekoGui_sub::groupUpdater, &NekoGui_sub::GroupUpdater::asyncUpdateCallback, this, [=](int gid) { if (gid == this->ent->id) refresh_data(); });\n\n    refresh_data();\n}\n\nGroupItem::~GroupItem() {\n    delete ui;\n}\n\nvoid GroupItem::refresh_data() {\n    ui->name->setText(ent->name);\n\n    auto type = ent->url.isEmpty() ? tr(\"Basic\") : tr(\"Subscription\");\n    if (ent->archive) type = tr(\"Archive\") + \" \" + type;\n    type += \" (\" + Int2String(ent->Profiles().length()) + \")\";\n    ui->type->setText(type);\n\n    if (ent->url.isEmpty()) {\n        ui->url->hide();\n        ui->subinfo->hide();\n        ui->update_sub->hide();\n    } else {\n        ui->url->setText(ent->url);\n        QStringList info;\n        if (ent->sub_last_update != 0) {\n            info << tr(\"Last update: %1\").arg(DisplayTime(ent->sub_last_update, QLocale::ShortFormat));\n        }\n        auto subinfo = ParseSubInfo(ent->info);\n        if (!ent->info.isEmpty()) {\n            info << subinfo;\n        }\n        if (info.isEmpty()) {\n            ui->subinfo->hide();\n        } else {\n            ui->subinfo->show();\n            ui->subinfo->setText(info.join(\" | \"));\n        }\n    }\n    runOnUiThread(\n        [=] {\n            adjustSize();\n            item->setSizeHint(sizeHint());\n            dynamic_cast<QWidget *>(parent())->adjustSize();\n        },\n        this);\n}\n\nvoid GroupItem::on_update_sub_clicked() {\n    NekoGui_sub::groupUpdater->AsyncUpdate(ent->url, ent->id);\n}\n\nvoid GroupItem::on_edit_clicked() {\n    auto dialog = new DialogEditGroup(ent, parentWindow);\n    connect(dialog, &QDialog::finished, this, [=] {\n        if (dialog->result() == QDialog::Accepted) {\n            ent->Save();\n            refresh_data();\n            MW_dialog_message(Dialog_DialogManageGroups, \"refresh\" + Int2String(ent->id));\n        }\n        dialog->deleteLater();\n    });\n    dialog->show();\n}\n\nvoid GroupItem::on_remove_clicked() {\n    if (NekoGui::profileManager->groups.size() <= 1) return;\n    if (QMessageBox::question(this, tr(\"Confirmation\"), tr(\"Remove %1?\").arg(ent->name)) ==\n        QMessageBox::StandardButton::Yes) {\n        NekoGui::profileManager->DeleteGroup(ent->id);\n        MW_dialog_message(Dialog_DialogManageGroups, \"refresh-1\");\n        delete item;\n    }\n}\n"
  },
  {
    "path": "ui/widget/GroupItem.h",
    "content": "#pragma once\n\n#include <QWidget>\n#include <QListWidgetItem>\n\n#include \"db/Database.hpp\"\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class GroupItem;\n}\nQT_END_NAMESPACE\n\nclass GroupItem : public QWidget {\n    Q_OBJECT\n\npublic:\n    explicit GroupItem(QWidget *parent, const std::shared_ptr<NekoGui::Group> &ent, QListWidgetItem *item);\n\n    ~GroupItem() override;\n\n    void refresh_data();\n\n    std::shared_ptr<NekoGui::Group> ent;\n    QListWidgetItem *item;\n\nprivate:\n    Ui::GroupItem *ui;\n\n    QWidget *parentWindow;\n\nsignals:\n\n    void edit_clicked();\n\nprivate slots:\n\n    void on_update_sub_clicked();\n\n    void on_edit_clicked();\n\n    void on_remove_clicked();\n};\n"
  },
  {
    "path": "ui/widget/GroupItem.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>GroupItem</class>\n <widget class=\"QWidget\" name=\"GroupItem\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>403</width>\n    <height>300</height>\n   </rect>\n  </property>\n  <property name=\"sizePolicy\">\n   <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Minimum\">\n    <horstretch>0</horstretch>\n    <verstretch>0</verstretch>\n   </sizepolicy>\n  </property>\n  <property name=\"windowTitle\">\n   <string notr=\"true\"/>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <item>\n      <widget class=\"QLabel\" name=\"type\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Maximum\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"styleSheet\">\n        <string notr=\"true\">color: rgb(251, 114, 153);</string>\n       </property>\n       <property name=\"text\">\n        <string notr=\"true\">Type</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QLabel\" name=\"name\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Ignored\" vsizetype=\"Maximum\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"text\">\n        <string notr=\"true\">Name</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QPushButton\" name=\"update_sub\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Fixed\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"text\">\n        <string>Update Subscription</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QPushButton\" name=\"edit\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Fixed\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"text\">\n        <string>Edit</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QPushButton\" name=\"remove\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Fixed\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"text\">\n        <string>Remove</string>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <widget class=\"QLabel\" name=\"url\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Ignored\" vsizetype=\"Maximum\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <property name=\"styleSheet\">\n      <string notr=\"true\">color: rgb(102, 102, 102);</string>\n     </property>\n     <property name=\"text\">\n      <string notr=\"true\">Url</string>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QLabel\" name=\"subinfo\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <property name=\"text\">\n      <string notr=\"true\">订阅流量信息</string>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "ui/widget/MessageBoxTimer.h",
    "content": "#pragma once\n\n#include <QMessageBox>\n#include <QTimer>\n\nclass MessageBoxTimer : public QTimer {\npublic:\n    QMessageBox *msgbox = nullptr;\n    bool showed = false;\n\n    explicit MessageBoxTimer(QObject *parent, QMessageBox *msgbox, int delayMs) : QTimer(parent) {\n        connect(this, &QTimer::timeout, this, &MessageBoxTimer::timeoutFunc, Qt::ConnectionType::QueuedConnection);\n        this->msgbox = msgbox;\n        setSingleShot(true);\n        setInterval(delayMs);\n        start();\n    };\n\n    void cancel() {\n        QTimer::stop();\n        if (msgbox != nullptr && showed) {\n            msgbox->reject(); // return the timeoutFunc\n        }\n    };\n\nprivate:\n    void timeoutFunc() {\n        if (msgbox == nullptr) return;\n        showed = true;\n        msgbox->exec();\n        msgbox = nullptr;\n    }\n};\n"
  },
  {
    "path": "ui/widget/MyLineEdit.h",
    "content": "#pragma once\n\n#include <QLineEdit>\n\nclass MyLineEdit : public QLineEdit {\npublic slots:\n\n    explicit MyLineEdit(QWidget *parent = nullptr) : QLineEdit(parent) {\n    }\n\n    void setText(const QString &s) {\n        QLineEdit::setText(s);\n        QLineEdit::home(false);\n    }\n};\n"
  },
  {
    "path": "ui/widget/MyTableWidget.h",
    "content": "#pragma once\n\n#include <QWidget>\n#include <QTableWidget>\n#include <QDropEvent>\n#include <QDebug>\n#include <functional>\n#include <utility>\n\nclass MyTableWidget : public QTableWidget {\npublic:\n    explicit MyTableWidget(QWidget *parent = nullptr) : QTableWidget(parent) {\n        // 拖拽设置\n        this->setDragDropMode(QAbstractItemView::InternalMove); // 内部移动\n        this->setDropIndicatorShown(true);                      // drop位置 提示\n        this->setSelectionBehavior(QAbstractItemView::SelectRows);\n    };\n\n    QList<int> order;          // id sorted (save)\n    std::map<int, int> id2Row; // id2Row\n    QList<int> row2Id;         // row2Id: use this to refresh data\n\n    std::function<void()> callback_save_order;\n    std::function<void(int id)> refresh_data;\n\n    void _save_order(bool saveToFile) {\n        order.clear();\n        id2Row.clear();\n        for (int i = 0; i < this->rowCount(); i++) {\n            auto id = row2Id[i];\n            order += id;\n            id2Row[id] = i;\n        }\n        if (callback_save_order != nullptr && saveToFile)\n            callback_save_order();\n    }\n\n    void update_order(bool saveToFile) {\n        if (order.isEmpty()) {\n            _save_order(false);\n            return;\n        }\n\n        // 纠错: order 里面含有不在当前表格控件的 id\n        bool needSave = false;\n        auto deleted_profiles = order;\n        for (int i = 0; i < this->rowCount(); i++) {\n            auto id = row2Id[i];\n            deleted_profiles.removeAll(id);\n        }\n        for (auto deleted_profile: deleted_profiles) {\n            needSave = true;\n            order.removeAll(deleted_profile);\n        }\n\n        // map(dstRow -> srcId)\n        QMap<int, int> newRows;\n        for (int i = 0; i < this->rowCount(); i++) {\n            auto id = row2Id[i];\n            auto dst = order.indexOf(id);\n            if (dst == i) continue;\n            if (dst == -1) {\n                // 纠错: 新的profile不需要移动\n                needSave = true;\n                continue;\n            }\n            newRows[dst] = id;\n        }\n\n        for (int i = 0; i < this->rowCount(); i++) {\n            if (!newRows.contains(i)) continue;\n            row2Id[i] = newRows[i];\n        }\n\n        // Then save the order\n        _save_order(needSave || saveToFile);\n    };\n\nprotected:\n    /*\n     * 2021.7.6 by gy\n     * 拖拽 继承QTableWidget overwrite dropEvent事件\n     * 功能：拖动一行到鼠标落下的位置\n     * 注意：DragDropMode相关参数的设置\n     */\n    void dropEvent(QDropEvent *event) override {\n        if (order.isEmpty()) order = row2Id;\n\n        // 原行号与目标行号的确定\n        int row_src, row_dst;\n        row_src = this->currentRow();                        // 原行号 可加if\n        auto id_src = row2Id[row_src];                       // id_src\n        QTableWidgetItem *item = this->itemAt(event->pos()); // 获取落点的item\n        if (item != nullptr) {\n            // 判断是否为空\n            row_dst = item->row(); // 不为空 获取其行号\n            // Modify order\n            order.removeAt(row_src);\n            order.insert(row_dst, id_src);\n        } else {\n            // 落点没有item 说明拖动到了最下面\n            return;\n        }\n\n        // Do update order & refresh\n        clearSelection();\n        update_order(true);\n        refresh_data(-1);\n    };\n};\n"
  },
  {
    "path": "ui/widget/ProxyItem.cpp",
    "content": "#include \"ProxyItem.h\"\n#include \"ui_ProxyItem.h\"\n\n#include <QMessageBox>\n\nProxyItem::ProxyItem(QWidget *parent, const std::shared_ptr<NekoGui::ProxyEntity> &ent, QListWidgetItem *item)\n    : QWidget(parent), ui(new Ui::ProxyItem) {\n    ui->setupUi(this);\n    this->setLayoutDirection(Qt::LeftToRight);\n\n    this->item = item;\n    this->ent = ent;\n    if (ent == nullptr) return;\n\n    refresh_data();\n}\n\nProxyItem::~ProxyItem() {\n    delete ui;\n}\n\nvoid ProxyItem::refresh_data() {\n    ui->type->setText(ent->bean->DisplayType());\n    ui->name->setText(ent->bean->DisplayName());\n    ui->address->setText(ent->bean->DisplayAddress());\n    ui->traffic->setText(ent->traffic_data->DisplayTraffic());\n    ui->test_result->setText(ent->DisplayLatency());\n\n    runOnUiThread(\n        [=] {\n            adjustSize();\n            item->setSizeHint(sizeHint());\n            dynamic_cast<QWidget *>(parent())->adjustSize();\n        },\n        this);\n}\n\nvoid ProxyItem::on_remove_clicked() {\n    if (!this->remove_confirm ||\n        QMessageBox::question(this, tr(\"Confirmation\"), tr(\"Remove %1?\").arg(ent->bean->DisplayName())) == QMessageBox::StandardButton::Yes) {\n        // TODO do remove (or not) -> callback\n        delete item;\n    }\n}\n\nQPushButton *ProxyItem::get_change_button() {\n    return ui->change;\n}\n"
  },
  {
    "path": "ui/widget/ProxyItem.h",
    "content": "#pragma once\n\n#include <QWidget>\n#include <QListWidgetItem>\n\n#include \"db/ProxyEntity.hpp\"\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\n    class ProxyItem;\n}\nQT_END_NAMESPACE\n\nclass QPushButton;\n\nclass ProxyItem : public QWidget {\n    Q_OBJECT\n\npublic:\n    explicit ProxyItem(QWidget *parent, const std::shared_ptr<NekoGui::ProxyEntity> &ent, QListWidgetItem *item);\n\n    ~ProxyItem() override;\n\n    void refresh_data();\n\n    QPushButton *get_change_button();\n\n    std::shared_ptr<NekoGui::ProxyEntity> ent;\n    QListWidgetItem *item;\n    bool remove_confirm = false;\n\nprivate:\n    Ui::ProxyItem *ui;\n\nprivate slots:\n\n    void on_remove_clicked();\n};\n"
  },
  {
    "path": "ui/widget/ProxyItem.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>ProxyItem</class>\n <widget class=\"QWidget\" name=\"ProxyItem\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>400</width>\n    <height>300</height>\n   </rect>\n  </property>\n  <property name=\"sizePolicy\">\n   <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Minimum\">\n    <horstretch>0</horstretch>\n    <verstretch>0</verstretch>\n   </sizepolicy>\n  </property>\n  <property name=\"windowTitle\">\n   <string notr=\"true\"/>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\n     <item>\n      <widget class=\"QLabel\" name=\"name\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Preferred\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"text\">\n        <string notr=\"true\">名称</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QPushButton\" name=\"change\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Fixed\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"icon\">\n        <iconset resource=\"../../res/neko.qrc\">\n         <normaloff>:/icon/material/swap-horizontal.svg</normaloff>:/icon/material/swap-horizontal.svg</iconset>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QPushButton\" name=\"remove\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Fixed\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"icon\">\n        <iconset resource=\"../../res/neko.qrc\">\n         <normaloff>:/icon/material/delete.svg</normaloff>:/icon/material/delete.svg</iconset>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <item>\n      <widget class=\"QLabel\" name=\"address\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"styleSheet\">\n        <string notr=\"true\">color: rgb(102, 102, 102);</string>\n       </property>\n       <property name=\"text\">\n        <string notr=\"true\">地址</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QLabel\" name=\"test_result\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Preferred\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"text\">\n        <string notr=\"true\">测试结果</string>\n       </property>\n       <property name=\"alignment\">\n        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n     <item>\n      <widget class=\"QLabel\" name=\"type\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"styleSheet\">\n        <string notr=\"true\">color: rgb(251, 114, 153);</string>\n       </property>\n       <property name=\"text\">\n        <string notr=\"true\">类型</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QLabel\" name=\"traffic\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Preferred\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"text\">\n        <string notr=\"true\">流量</string>\n       </property>\n       <property name=\"alignment\">\n        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <resources>\n  <include location=\"../../res/neko.qrc\"/>\n </resources>\n <connections/>\n</ui>\n"
  }
]