Compare commits

..

No commits in common. "v2.11.1" and "init" have entirely different histories.

263 changed files with 3879 additions and 6213 deletions

4
.gitattributes vendored
View file

@ -1,4 +0,0 @@
* text=auto eol=lf
*.bat text eol=crlf
*.jar binary

View file

@ -0,0 +1,57 @@
---
name: "[English] Bug report"
about: Create a report to help us improve
title: "[BUG] "
labels: ''
assignees: ''
---
<!-- Be sure to put a clear title after [BUG] in the text box above -->
<!-- Be sure to put a clear title after [BUG] in the text box above -->
<!-- Be sure to put a clear title after [BUG] in the text box above -->
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Device Info (please complete the following information):**
- Device: [e.g. Pixel 4]
- ROM: [e.g: AOSP]
- ROM Version:
- Android Version [e.g. 10]
**Application Info (please complete the following information):**
- Version: [e.g. 1.1.10]
- Apk File Name: [e.g. app-release-arm64-v8a.apk]
- Distribution Channel: [e.g. Google Play]
**Additional context**
Add any other context about the problem here.
**Configure**
Paste configure file which **removed server info**
```yaml
# paste here
```
**Logs**
Paste logs to help detect problem
```
<paste here>
```

View file

@ -1,107 +0,0 @@
name: "[English] Bug Report"
description: "Create a report to help us debug bugs"
title: "[BUG] "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
NOTE: Be sure to put a clear and concise title **AFTER** `[BUG]` in the text box above.
NOTE: We do not provide any services such as proxies, DO NOT feedback any problems not caused by this application here.
<!-- template -->
- type: textarea
id: description
attributes:
label: "Describe the bug"
description: "A clear and concise description of what the bug is."
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: "To Reproduce"
description: "Steps to reproduce the behavior:"
value: |
Step 1: ...
Step 2: ...
Step 3: ...
...
validations:
required: true
- type: textarea
id: device-info
attributes:
label: "Device Info"
description: |
Input your device information.
Example:
- Device: Pixel 4
- ROM: AOSP
- Android Version: 10
value: |
- Device:
- ROM:
- Android Version:
validations:
required: true
- type: textarea
id: app-info
attributes:
label: "Application Info"
description: |
Input application you are using information.
Example:
```
- Version: 2.5.4-premium
- APK filename: cfa-2.5.4-premium-arm64-v8a-release.apk
- Distribution Channel: Google Play
```
value: |
- Version:
- APK filename:
- Distribution Channel:
validations:
required: true
- type: textarea
id: configure
attributes:
render: yml
label: "Configure File"
description: |
Please paste or upload the configuration file here.
TIPS: If you only have a subscription link, please use your browser to download it.
**NOTE: Please remove proxies from the configuration file before uploading it.**
**NOTE: Please remove proxies from the configuration file before uploading it.**
**NOTE: Please remove proxies from the configuration file before uploading it.**
validations:
required: true
- type: textarea
id: logs
attributes:
render: raw
label: "Logs"
description: |
Please paste or upload the log file here.
TIPS: Please use the `Logcat` in application or `adb logcat`. `adb logcat` would be better.
validations:
required: true
- type: textarea
id: screenshot
attributes:
label: "Screenshot"
description: "If applicable, add screenshots to help explain your problem."
placeholder: "Optional"
- type: textarea
id: additional
attributes:
label: "Additional"
description: "Add any other context about the problem here."

View file

@ -0,0 +1,24 @@
---
name: "[English] Feature request"
about: Suggest an idea for this app
title: "[Feature Request] "
labels: ''
assignees: ''
---
<!-- Be sure to put a clear title after [Feature Request] in the text box above -->
<!-- Be sure to put a clear title after [Feature Request] in the text box above -->
<!-- Be sure to put a clear title after [Feature Request] in the text box above -->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -1,27 +0,0 @@
name: "[English] Feature Request"
description: "Create a report to help us improve"
title: "[Feature Request] "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request!
NOTE: Be sure to put a clear and concise title **AFTER** `[Feature Request]` in the text box above.
<!-- template -->
- type: textarea
id: "description"
attributes:
label: "Feature Description"
description: |
A clear and concise description of the feature.
validations:
required: true
- type: textarea
id: "additional"
attributes:
label: "Additional"
description: |
Add any other context or screenshots about the feature request here.

View file

@ -0,0 +1,55 @@
---
name: "[简体中文] 创建错误报告"
about: 创建错误报告以帮助我们改进应用
title: "[BUG] "
labels: ''
assignees: ''
---
<!-- 请务必在上方文本框处 [BUG] 后填入清晰明了的标题 -->
<!-- 请务必在上方文本框处 [BUG] 后填入清晰明了的标题 -->
<!-- 请务必在上方文本框处 [BUG] 后填入清晰明了的标题 -->
**描述出现的错误**
请简洁的描述你遇到的错误
**如何复现该错误**
复现步骤:
1. ...
2. ...
3. ...
4. ...
**预期行为**
清晰简单的描述你预期的应用应该表现的行为
**屏幕截图**
如果适用, 上传屏幕截图以帮助描述错误
**设备信息 (请完成以下信息):**
- 机型: [例如: Pixel 4]
- 系统/ROM: [例如: MIUI 11]
- Android 版本 [例如: 10]
- ROM版本 [例如: 20.3.19]
**应用信息**
- 版本: [例如: 1.1.10]
- 安装包文件名: [例如: app-release-arm64-v8a.apk]
- 应用来源: [例如: Google Play]
**附加信息**
其他的可能与改错误相关的信息
**配置文件**
在此粘贴 **去除服务器信息的** 的 **配置文件**
```yaml
# 在此粘贴
```
**日志**
粘贴日志以帮助侦测错误
```
<在此粘贴>
```

View file

@ -1,108 +0,0 @@
name: "[简体中文] 错误报告"
description: "创建错误报告以帮助我们修正应用"
title: "[BUG] "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
感谢您在百忙之中填写此错误报告。
注意: 请务必在上方文本框的 `[BUG]` **之后**填写清晰明了的标题。
注意:这里不提供像是代理服务器之类的服务,请不要反馈非应用自身引起的问题。
<!-- template -->
- type: textarea
id: description
attributes:
label: "描述此错误"
description: "请清晰简洁的描述你遇到的错误。"
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: "如何复现该错误"
description: "复现步骤:"
value: |
步骤 1: ...
步骤 2: ...
步骤 3: ...
...
validations:
required: true
- type: textarea
id: device-info
attributes:
label: "设备信息"
description: |
输入您正在使用的设备信息。
例子:
- 机型: Pixel 4
- 系统类型: MIUI/AOSP
- Android 版本: 10
value: |
- 机型:
- 系统类型:
- Android 版本:
validations:
required: true
- type: textarea
id: app-info
attributes:
label: "应用信息"
description: |
输入您正在使用的应用信息。
例子:
```
- 版本: 2.5.4-premium
- 安装包文件名: cfa-2.5.4-premium-arm64-v8a-release.apk
- 应用来源: Google Play
```
value: |
- 版本:
- 安装包文件名:
- 应用来源:
validations:
required: true
- type: textarea
id: configure
attributes:
render: yml
label: "配置文件"
description: |
请在此粘贴和上传配置文件。
提示:如果您仅有一个订阅链接,请使用浏览器打开此链接以下载配置文件。
**注意: 请在上传配置文件前,移除其中的代理服务器信息。**
**注意: 请在上传配置文件前,移除其中的代理服务器信息。**
**注意: 请在上传配置文件前,移除其中的代理服务器信息。**
validations:
required: true
- type: textarea
id: logs
attributes:
render: raw
label: "日志"
description: |
请在此粘贴或上传日志。
提示: 请使用应用内的 `Logcat` 或 `adb logcat` 捕获日志. `adb logcat` 能更好地帮助侦测问题.
validations:
required: true
- type: textarea
id: screenshot
attributes:
label: "屏幕截图"
description: "如果适用,请在此粘贴或上传屏幕截图。"
placeholder: "可选"
- type: textarea
id: additional
attributes:
label: "附加信息"
description: "其他的可能与改错误相关的信息。"
placeholder: "可选"

View file

@ -0,0 +1,21 @@
---
name: "[简体中文] 功能请求"
about: 你希望的能够在应用中增加的功能
title: "[Feature Request] "
labels: ''
assignees: ''
---
<!-- 请务必在上方文本框处 [Feature Request] 后填入清晰明了的标题 -->
<!-- 请务必在上方文本框处 [Feature Request] 后填入清晰明了的标题 -->
<!-- 请务必在上方文本框处 [Feature Request] 后填入清晰明了的标题 -->
**功能描述**
请清晰的描述你想要的功能
**描述你希望的实现方式**
清晰的描述应用应该如何实现该功能
**附加信息**
其他的与改功能相关的附加信息

View file

@ -1,27 +0,0 @@
name: "[简体中文] 功能请求"
description: "您希望的能够在应用中增加功能"
title: "[Feature Request] "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
感谢您在百忙之中填写此功能请求报告。
注意: 请务必在上方文本框的 `[Feature Request]` **之后**填写清晰明了的标题。
<!-- template -->
- type: textarea
id: "description"
attributes:
label: "功能描述"
description: |
简介明了的描述此功能。
validations:
required: true
- type: textarea
id: "additional"
attributes:
label: "附加信息"
description: |
与此功能相关的其他附加信息。

View file

@ -1,99 +0,0 @@
name: Build Debug
on:
pull_request:
types: [opened, reopened, synchronize]
workflow_dispatch:
push:
branches:
- main
jobs:
BuildDebug:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Checkout submodules
run: git submodule update --init --recursive --remote --force
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.23"
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
# - name: Signing properties
# env:
# SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
# SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
# SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
# run: |
# touch signing.properties
# echo keystore.password="$SIGNING_STORE_PASSWORD" > signing.properties
# echo key.alias="$SIGNING_KEY_ALIAS" >> signing.properties
# echo key.password="$SIGNING_KEY_PASSWORD" >> signing.properties
# echo "cat signing.properties"
# cat signing.properties
- name: Build
if: success()
run: ./gradlew --no-daemon app:assembleAlphaRelease
- name: Upload Aritfact (universal)
uses: actions/upload-artifact@v4
if: ${{ success() }}
with:
name: CMFA Debug Unsigned APK (universal)
path: |
app/build/outputs/apk/alpha/release/*-universal-*.apk
- name: Upload Aritfact (arm64-v8a)
uses: actions/upload-artifact@v4
if: ${{ success() }}
with:
name: CMFA Debug Unsigned APK (arm64-v8a)
path: |
app/build/outputs/apk/alpha/release/*-arm64-v8a-*.apk
- name: Upload Aritfact (armeabi-v7a)
uses: actions/upload-artifact@v4
if: ${{ success() }}
with:
name: CMFA Debug Unsigned APK (armeabi-v7a)
path: |
app/build/outputs/apk/alpha/release/*-armeabi-v7a-*.apk
- name: Upload Aritfact (x86_64)
uses: actions/upload-artifact@v4
if: ${{ success() }}
with:
name: CMFA Debug Unsigned APK (x86_64)
path: |
app/build/outputs/apk/alpha/release/*-x86_64-*.apk
- name: Upload Aritfact (x86)
uses: actions/upload-artifact@v4
if: ${{ success() }}
with:
name: CMFA Debug Unsigned APK (x86)
path: |
app/build/outputs/apk/alpha/release/*-x86-*.apk

View file

@ -1,84 +0,0 @@
name: Build Pre-Release
on:
workflow_dispatch:
pull_request:
branches: [main]
types: [closed]
jobs:
BuildPreRelease:
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Checkout submodules
run: git submodule update --init --recursive --remote --force
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.23"
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Signing properties
env:
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
run: |
touch signing.properties
echo keystore.password="$SIGNING_STORE_PASSWORD" > signing.properties
echo key.alias="$SIGNING_KEY_ALIAS" >> signing.properties
echo key.password="$SIGNING_KEY_PASSWORD" >> signing.properties
echo "cat signing.properties"
cat signing.properties
- name: Pre-release Build
if: success()
run: ./gradlew --no-daemon app:assembleAlphaRelease
# Delete old Prerelease-alpha
- uses: dev-drprasad/delete-tag-and-release@v1.1
with:
tag_name: Prerelease-alpha
github_token: ${{ secrets.GITHUB_TOKEN }}
delete_release: true
- name: Tag Repo
uses: richardsimko/update-tag@v1
with:
tag_name: Prerelease-alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Alpha
uses: softprops/action-gh-release@v2
if: ${{ success() }}
with:
tag_name: Prerelease-alpha
files: app/build/outputs/apk/alpha/release/*
prerelease: true
generate_release_notes: true
- name: Release Changelog Builder
uses: mikepenz/release-changelog-builder-action@v4

View file

@ -1,121 +0,0 @@
name: Build Release
on:
workflow_dispatch:
inputs:
release-tag:
description: 'Release Tag (v2.x.x)'
required: true
jobs:
BuildRelease:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Checkout submodules
run: git submodule update --init --recursive --remote
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.23"
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Convert and set version env
id: process-version
run: |
VERSION_TAG=${{ inputs.release-tag }}
VERSION_TAG=${VERSION_TAG#v} # remove the 'v' prefix
IFS='.' read -ra VERSION_PARTS <<< "$VERSION_TAG" # split into array
VERSION_CODE=$(printf "%1d%02d%03d" "${VERSION_PARTS[0]}" "${VERSION_PARTS[1]}" "${VERSION_PARTS[2]}")
echo "versonName=$VERSION_TAG" >> $GITHUB_OUTPUT # "1.2.3"
echo "versonCode=$VERSION_CODE" >> $GITHUB_OUTPUT # "102003"
# Re-write version in build.gradle.kts
- name: Re-write version
uses: Devofure/advance-android-version-actions@v1.5
with:
gradlePath: build.gradle.kts
versionCode: ${{ steps.process-version.outputs.versonCode }}
versionName: ${{ steps.process-version.outputs.versonName }}
# If any change found, commit it and push
- name: Commit and push if changes
run: |
changes=$(git diff --name-only origin/main | wc -l)
if [ $changes -gt 0 ]
then
newVersionName=${{ steps.process-version.outputs.versonName }}
newVersionCode=${{ steps.process-version.outputs.versonCode }}
git config --global user.name 'GitHub Action'
git config --global user.email 'action@github.com'
git add build.gradle.kts
git commit -am "Bump version to $newVersionName ($newVersionCode)"
git tag "v$newVersionName"
git push --follow-tags
fi
- name: Signing properties
env:
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
run: |
touch signing.properties
echo keystore.password="$SIGNING_STORE_PASSWORD" > signing.properties
echo key.alias="$SIGNING_KEY_ALIAS" >> signing.properties
echo key.password="$SIGNING_KEY_PASSWORD" >> signing.properties
echo "cat signing.properties"
cat signing.properties
- name: Release Build
if: success()
run: ./gradlew --no-daemon app:assembleMetaRelease
- name: Tag Repo
uses: richardsimko/update-tag@v1
with:
tag_name: ${{ inputs.release-tag }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Release
uses: softprops/action-gh-release@v1
if: ${{ success() }}
with:
tag_name: ${{ inputs.release-tag }}
files: app/build/outputs/apk/meta/release/*
generate_release_notes: true
- name: Release Changelog Builder
uses: mikepenz/release-changelog-builder-action@v4.1.1
with:
configurationJson: |
{
"ignore_labels": [
"Update"
],
}

View file

@ -1,81 +0,0 @@
name: Update Clash-Core and Go Modules
on:
repository_dispatch:
types:
- core-updated
workflow_dispatch:
jobs:
update-dependencies:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Checkout submodules
run: git submodule update --init --recursive --remote --force
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.23"
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Install update-go-mod-replace
run: |
go install github.com/metacubex/update-go-mod-replace@latest
- name: Update Foss Gomod
run: |
cd ${{ github.workspace }}/core/src/foss/golang/
update-go-mod-replace ${{ github.workspace }}/core/src/foss/golang/clash/go.mod $(pwd)/go.mod
go mod tidy
- name: Update Main Gomod
run: |
cd ${{ github.workspace }}/core/src/main/golang/
update-go-mod-replace ${{ github.workspace }}/core/src/foss/golang/clash/go.mod $(pwd)/go.mod
go mod tidy
- uses: tibdex/github-app-token@v2
id: generate-token
with:
app_id: ${{ secrets.MAINTAINER_APPID }}
private_key: ${{ secrets.MAINTAINER_APP_PRIVATE_KEY }}
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v6
with:
token: ${{ steps.generate-token.outputs.token }}
commit-message: Update Dependencies
branch: update-dependencies
delete-branch: true
title: 'Update Dependencies'
draft: false
body: |
- Update Clash-Meta Core
- Update Go Module Dependecies
labels: |
Update
- name: PR result
if: ${{ steps.cpr.outputs.pull-request-number }}
run: |
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"

23
.gitignore vendored
View file

@ -1,7 +1,6 @@
.gradle
build/
/app/foss/release
/app/premium/release
/app/release/
/captures
# Ignore Gradle GUI config
@ -19,19 +18,11 @@ gradle-app.setting
# Ignore IDEA config
*.iml
/.idea/*
!/.idea/codeStyles
/core/src/main/golang/.idea/*
!/.idea/codeStyles
!/core/src/main/golang/.idea/codeStyles
/core/src/foss/golang/.idea/*
!/core/src/foss/golang/.idea/codeStyles
/core/src/premium/golang/.idea/*
!/core/src/premium/golang/.idea/codeStyles
# Ignore builtin geofiles
app/src/main/assets
# KeyStore
signing.properties
*.keystore
*.jks
@ -41,9 +32,8 @@ cmake-build-*
# local.properties
local.properties
# tracker
tracker.properties
# keystore
keystore.properties
# vscode
.vscode
@ -60,7 +50,4 @@ google-services.json
.directory
# logs
*.log
# MacOS
.DS_Store
*.log

7
.gitmodules vendored
View file

@ -1,4 +1,3 @@
[submodule "clash-foss"]
path = core/src/foss/golang/clash
url = https://github.com/MetaCubeX/mihomo
branch = Alpha
[submodule "core/src/main/golang/clash"]
path = core/src/main/golang/clash
url = https://github.com/Kr328/clash.git

View file

@ -20,6 +20,15 @@
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">

View file

@ -10,5 +10,5 @@ Please use `Android Studio` or `Intellij IDEA` to open the project and use the p
#### License
Contributing to Clash for Android that assumes you allow code to be merged into closed-source branch of Clash for Android. Other terms follow the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html)
Contributing to Clash for Android that assumes you allow code to be merged into closed-source branches. Other terms follow the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html)

View file

@ -1,20 +1,32 @@
## Clash Meta for Android
## Clash for Android
A Graphical user interface of [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta) for Android
A Graphical user interface of [clash](https://github.com/Dreamacro/clash) for Android
<a href="https://play.google.com/store/apps/details?id=com.github.kr328.clash"><img width="200px" alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"/></a> or [Releases](https://github.com/Kr328/ClashForAndroid/releases)
### Feature
Feature of [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta)
Fully feature of [clash](https://github.com/Dreamacro/clash) ~~(Exclude `external-controller`~~
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/com.github.metacubex.clash.meta/)
### Requirement
- Android 5.0+ (minimum)
- Android 7.0+ (recommend)
- `armeabi-v7a` , `arm64-v8a`, `x86` or `x86_64` Architecture
* Android 5.0+ (minimum)
* Android 7.0+ (recommend)
* `armeabi-v7a` , `arm64-v8a`, `x86` or `x86_64` Architecture
### License
See also [LICENSE](./LICENSE) and [NOTICE](./NOTICE)
### Privacy Policy
See also [PRIVACY_POLICY.md](./PRIVACY_POLICY.md)
### Build
@ -24,56 +36,28 @@ Feature of [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta)
git submodule update --init --recursive
```
2. Install **OpenJDK 11**, **Android SDK**, **CMake** and **Golang**
2. Install `JDK 1.8`, `Android SDK` and `Golang`
3. Create `local.properties` in project root with
3. Create `local.properties` in project root with
```properties
sdk.dir=/path/to/android-sdk
appcenter.key=<AppCenter Key> # Optional, from "appcenter.ms"
```
4. Create `signing.properties` in project root with
4. Create `keystore.properties` in project root with
```properties
keystore.path=/path/to/keystore/file
keystore.password=<key store password>
key.alias=<key alias>
key.password=<key password>
```
storeFile=/path/to/keystore/file
storePassword=<key store password>
keyAlias=<key alias>
keyPassword=<key password>
```
5. Build
```bash
./gradlew app:assembleMeta-AlphaRelease
./gradlew app:assembleRelease
```
### Automation
APP package name is `com.github.metacubex.clash.meta`
- Toggle Clash.Meta service status
- Send intent to activity `com.github.kr328.clash.ExternalControlActivity` with action `com.github.metacubex.clash.meta.action.TOGGLE_CLASH`
- Start Clash.Meta service
- Send intent to activity `com.github.kr328.clash.ExternalControlActivity` with action `com.github.metacubex.clash.meta.action.START_CLASH`
- Stop Clash.Meta service
- Send intent to activity `com.github.kr328.clash.ExternalControlActivity` with action `com.github.metacubex.clash.meta.action.STOP_CLASH`
- Import a profile
- URL Scheme `clash://install-config?url=<encoded URI>` or `clashmeta://install-config?url=<encoded URI>`
### Contribution and Project Maintenance
#### Meta Kernel
- CMFA uses the kernel from `android-real` branch under `MetaCubeX/Clash.Meta`, which is a merge of the main `Alpha` branch and `android-open`.
- If you want to contribute to the kernel, make PRs to `Alpha` branch of the Meta kernel repository.
- If you want to contribute Android-specific patches to the kernel, make PRs to `android-open` branch of the Meta kernel repository.
#### Maintenance
- When `MetaCubeX/Clash.Meta` kernel is updated to a new version, the `Update Dependencies` actions in this repo will be triggered automatically.
- It will pull the new version of the meta kernel, update all the golang dependencies, and create a PR without manual intervention.
- If there is any compile error in PR, you need to fix it before merging. Alternatively, you may merge the PR directly.
- Manually triggering `Build Pre-Release` actions will compile and publish a `PreRelease` version.
- Manually triggering `Build Release` actions will compile, tag and publish a `Release` version.
- You must fill the blank `Release Tag` with the tag you want to release in the format of `v1.2.3`.
- `versionName` and `versionCode` in `build.gradle.kts` will be automatically bumped to the tag you filled above.
6. Pick `app-release-<arch>.apk` in `app/build/outputs/apks`

View file

@ -1,69 +1,136 @@
import java.net.URL
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.*
plugins {
id("com.android.application")
kotlin("android")
kotlin("kapt")
id("com.android.application")
}
dependencies {
compileOnly(project(":hideapi"))
android {
compileSdk = buildTargetSdkVersion
implementation(project(":core"))
implementation(project(":service"))
implementation(project(":design"))
implementation(project(":common"))
flavorDimensions(buildFlavor)
implementation(libs.kotlin.coroutine)
implementation(libs.androidx.core)
implementation(libs.androidx.activity)
implementation(libs.androidx.fragment)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.coordinator)
implementation(libs.androidx.recyclerview)
implementation(libs.google.material)
}
defaultConfig {
applicationId = "com.github.kr328.clash"
tasks.getByName("clean", type = Delete::class) {
delete(file("release"))
}
minSdk = buildMinSdkVersion
targetSdk = buildTargetSdkVersion
val geoFilesDownloadDir = "src/main/assets"
versionCode = buildVersionCode
versionName = buildVersionName
task("downloadGeoFiles") {
resConfigs("zh-rCN", "zh-rHK", "zh-rTW")
val geoFilesUrls = mapOf(
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb" to "geoip.metadb",
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat" to "geosite.dat",
// "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country.mmdb" to "country.mmdb",
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb" to "ASN.mmdb",
)
resValue("string", "release_name", "v$buildVersionName")
resValue("integer", "release_code", "$buildVersionCode")
}
doLast {
geoFilesUrls.forEach { (downloadUrl, outputFileName) ->
val url = URL(downloadUrl)
val outputPath = file("$geoFilesDownloadDir/$outputFileName")
outputPath.parentFile.mkdirs()
url.openStream().use { input ->
Files.copy(input, outputPath.toPath(), StandardCopyOption.REPLACE_EXISTING)
println("$outputFileName downloaded to $outputPath")
buildTypes {
named("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
productFlavors {
create("open") {
dimension = "open"
versionNameSuffix = ".open-source"
}
create("premium") {
dimension = "premium"
versionNameSuffix = ".premium"
}
}
buildFeatures {
dataBinding = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
splits {
abi {
isEnable = true
isUniversalApk = true
}
}
buildTypes.apply {
val properties = Properties().apply {
rootProject.file("local.properties").inputStream().use {
load(it)
}
}
val key = properties.getProperty("appcenter.key", null)
forEach {
if (it.name == "debug" || key == null) {
it.buildConfigField("String", "APP_CENTER_KEY", "null")
} else {
it.buildConfigField("String", "APP_CENTER_KEY", "\"$key\"")
}
}
}
signingConfigs.apply {
val signingFile = rootProject.file("keystore.properties")
if ( signingFile.exists() ) {
val properties = Properties().apply {
signingFile.inputStream().use {
load(it)
}
}
signingConfigs {
named("release") {
storeFile = rootProject.file(Objects.requireNonNull(properties.getProperty("storeFile")))
storePassword = Objects.requireNonNull(properties.getProperty("storePassword"))
keyAlias = Objects.requireNonNull(properties.getProperty("keyAlias"))
keyPassword = Objects.requireNonNull(properties.getProperty("keyPassword"))
}
}
buildTypes {
named("release") {
this.signingConfig = signingConfigs.findByName("release")
}
}
}
}
}
afterEvaluate {
val downloadGeoFilesTask = tasks["downloadGeoFiles"]
dependencies {
api(project(":core"))
api(project(":service"))
api(project(":design"))
api(project(":common"))
tasks.forEach {
if (it.name.startsWith("assemble")) {
it.dependsOn(downloadGeoFilesTask)
}
}
implementation(kotlin("stdlib-jdk7"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion")
implementation("androidx.core:core-ktx:$ktxVersion")
implementation("androidx.activity:activity:$activityVersion")
implementation("androidx.appcompat:appcompat:$appcompatVersion")
implementation("androidx.coordinatorlayout:coordinatorlayout:$coordinatorlayoutVersion")
implementation("androidx.recyclerview:recyclerview:$recyclerviewVersion")
implementation("androidx.fragment:fragment:$fragmentVersion")
implementation("com.microsoft.appcenter:appcenter-analytics:$appcenterVersion")
implementation("com.microsoft.appcenter:appcenter-crashes:$appcenterVersion")
implementation("com.google.android.material:material:$materialVersion")
}
tasks.getByName("clean", type = Delete::class) {
delete(file(geoFilesDownloadDir))
task("cleanRelease", type = Delete::class) {
delete(file("release"))
}
afterEvaluate {
tasks["clean"].dependsOn(tasks["cleanRelease"])
}

View file

@ -31,29 +31,3 @@
public static void checkParameterIsNotNull(...);
public static void checkNotNullParameter(...);
}
# Kotlin Coroutine
# Allow R8 to optimize away the FastServiceLoader.
# Together with ServiceLoader optimization in R8
# this results in direct instantiation when loading Dispatchers.Main
-assumenosideeffects class kotlinx.coroutines.internal.MainDispatcherLoader {
boolean FAST_SERVICE_LOADER_ENABLED return false;
}
-assumenosideeffects class kotlinx.coroutines.internal.FastServiceLoaderKt {
boolean ANDROID_DETECTED return true;
}
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
# Disable support for "Missing Main Dispatcher", since we always have Android main dispatcher
-assumenosideeffects class kotlinx.coroutines.internal.MainDispatchersKt {
boolean SUPPORT_MISSING return false;
}
# Statically turn off all debugging facilities and assertions
-assumenosideeffects class kotlinx.coroutines.DebugKt {
boolean getASSERTIONS_ENABLED() return false;
boolean getDEBUG() return false;
boolean getRECOVER_STACK_TRACES() return false;
}

View file

@ -41,8 +41,7 @@
android:name=".MainActivity"
android:configChanges="uiMode"
android:exported="true"
android:label="@string/launch_name"
android:launchMode="singleTop">
android:label="@string/launch_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -55,9 +54,9 @@
</intent-filter>
</activity>
<activity
android:name=".ExternalControlActivity"
android:name=".ExternalImportActivity"
android:exported="true"
android:label="@string/external_control_activity"
android:label="@string/import_from_file"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -65,21 +64,9 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="clash"/>
<data android:scheme="clashmeta"/>
<data android:host="install-config"/>
</intent-filter>
<intent-filter>
<action android:name="com.github.metacubex.clash.meta.action.START_CLASH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="com.github.metacubex.clash.meta.action.STOP_CLASH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="com.github.metacubex.clash.meta.action.TOGGLE_CLASH" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="install-config"
android:scheme="clash" />
</intent-filter>
</activity>
<activity
@ -148,11 +135,6 @@
android:configChanges="uiMode"
android:exported="false"
android:label="@string/override" />
<activity
android:name=".MetaFeatureSettingsActivity"
android:configChanges="uiMode"
android:exported="false"
android:label="@string/meta_features" />
<activity
android:name=".AccessControlActivity"
android:configChanges="uiMode"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -76,8 +76,11 @@ class AccessControlActivity : BaseActivity<AccessControlDesign>() {
val data = clipboard?.primaryClip
if (data != null && data.itemCount > 0) {
val packages = data.getItemAt(0).text.split("\n").toSet()
val all = design.apps.map(AppInfo::packageName).intersect(packages)
val all = withContext(Dispatchers.IO) {
val packages = data.getItemAt(0).text.split("\n").toSet()
design.apps.map(AppInfo::packageName).intersect(packages)
}
selected.clear()
selected.addAll(all)
@ -88,12 +91,14 @@ class AccessControlActivity : BaseActivity<AccessControlDesign>() {
AccessControlDesign.Request.Export -> {
val clipboard = getSystemService<ClipboardManager>()
val data = ClipData.newPlainText(
"packages",
selected.joinToString("\n")
)
withContext(Dispatchers.IO) {
val data = ClipData.newPlainText(
"packages",
selected.joinToString("\n")
)
clipboard?.setPrimaryClip(data)
clipboard?.setPrimaryClip(data)
}
}
}
}

View file

@ -1,9 +1,12 @@
package com.github.kr328.clash
import android.os.DeadObjectException
import com.github.kr328.clash.common.compat.versionCodeCompat
import com.github.kr328.clash.common.log.Log
import com.github.kr328.clash.design.AppCrashedDesign
import com.github.kr328.clash.log.SystemLogcat
import com.microsoft.appcenter.crashes.Crashes
import com.microsoft.appcenter.crashes.ingestion.models.ErrorAttachmentLog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
@ -24,6 +27,16 @@ class AppCrashedActivity : BaseActivity<AppCrashedDesign>() {
SystemLogcat.dumpCrash()
}
if (BuildConfig.APP_CENTER_KEY != null && !BuildConfig.DEBUG) {
if (logs.isNotBlank()) {
Crashes.trackError(
DeadObjectException(),
mapOf("type" to "app_crashed"),
listOf(ErrorAttachmentLog.attachmentWithText(logs, "logcat.txt"))
)
}
}
design.setAppLogs(logs)
while (isActive) {

View file

@ -24,23 +24,34 @@ import com.github.kr328.clash.util.ActivityResultLifecycle
import com.github.kr328.clash.util.ApplicationObserver
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
abstract class BaseActivity<D : Design<*>> :
AppCompatActivity(),
CoroutineScope by MainScope(),
Broadcasts.Observer {
enum class Event {
ServiceRecreated,
ActivityStart,
ActivityStop,
ClashStop,
ClashStart,
ProfileLoaded,
ProfileChanged
}
protected val uiStore by lazy { UiStore(this) }
protected val events = Channel<Event>(Channel.UNLIMITED)
protected var activityStarted: Boolean = false
protected val clashRunning: Boolean
get() = Remote.broadcasts.clashRunning
protected var design: D? = null
set(value) {
private set(value) {
field = value
if (value != null) {
setContentView(value.root)
} else {
@ -61,14 +72,14 @@ abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
suspend fun <I, O> startActivityForResult(
contracts: ActivityResultContract<I, O>,
input: I,
input: I
): O = withContext(Dispatchers.Main) {
val requestKey = nextRequestKey.getAndIncrement().toString()
ActivityResultLifecycle().use { lifecycle, start ->
suspendCoroutine { c ->
activityResultRegistry.register(requestKey, lifecycle, contracts) {
c.resume(it)
c.resumeWith(Result.success(it))
}.apply { start() }.launch(input)
}
}
@ -78,6 +89,7 @@ abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
suspendCoroutine<Unit> {
window.decorView.post {
this.design = design
it.resume(Unit)
}
}
@ -85,35 +97,49 @@ abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
applyDayNight()
launch {
main()
finish()
}
}
override fun onStart() {
super.onStart()
activityStarted = true
Remote.broadcasts.addObserver(this)
events.trySend(Event.ActivityStart)
events.offer(Event.ActivityStart)
}
override fun onStop() {
super.onStop()
activityStarted = false
Remote.broadcasts.removeObserver(this)
events.trySend(Event.ActivityStop)
events.offer(Event.ActivityStop)
}
override fun onDestroy() {
design?.cancel()
cancel()
super.onDestroy()
}
override fun finish() {
if (deferRunning) return
if (deferRunning) {
return
}
deferRunning = true
launch {
@ -143,35 +169,28 @@ abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
override fun onSupportNavigateUp(): Boolean {
this.onBackPressed()
return true
}
override fun onProfileChanged() {
events.trySend(Event.ProfileChanged)
}
override fun onProfileUpdateCompleted(uuid: UUID?) {
events.trySend(Event.ProfileUpdateCompleted)
}
override fun onProfileUpdateFailed(uuid: UUID?, reason: String?) {
events.trySend(Event.ProfileUpdateFailed)
events.offer(Event.ProfileChanged)
}
override fun onProfileLoaded() {
events.trySend(Event.ProfileLoaded)
events.offer(Event.ProfileLoaded)
}
override fun onServiceRecreated() {
events.trySend(Event.ServiceRecreated)
events.offer(Event.ServiceRecreated)
}
override fun onStarted() {
events.trySend(Event.ClashStart)
events.offer(Event.ClashStart)
}
override fun onStopped(cause: String?) {
events.trySend(Event.ClashStop)
events.offer(Event.ClashStop)
if (cause != null && activityStarted) {
launch {
@ -182,45 +201,49 @@ abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
private fun queryDayNight(config: Configuration = resources.configuration): DayNight {
return when (uiStore.darkMode) {
DarkMode.Auto -> if (config.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) DayNight.Night else DayNight.Day
DarkMode.ForceLight -> DayNight.Day
DarkMode.ForceDark -> DayNight.Night
DarkMode.Auto -> {
if (config.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES)
DayNight.Night
else
DayNight.Day
}
DarkMode.ForceLight -> {
DayNight.Day
}
DarkMode.ForceDark -> {
DayNight.Night
}
}
}
private fun applyDayNight(config: Configuration = resources.configuration) {
val dayNight = queryDayNight(config)
when (dayNight) {
DayNight.Night -> theme.applyStyle(R.style.AppThemeDark, true)
DayNight.Day -> theme.applyStyle(R.style.AppThemeLight, true)
DayNight.Night -> {
theme.applyStyle(R.style.AppThemeDark, true)
}
DayNight.Day -> {
theme.applyStyle(R.style.AppThemeLight, true)
}
}
window.isAllowForceDarkCompat = false
window.isSystemBarsTranslucentCompat = true
window.statusBarColor = resolveThemedColor(android.R.attr.statusBarColor)
window.navigationBarColor = resolveThemedColor(android.R.attr.navigationBarColor)
if (Build.VERSION.SDK_INT >= 23) {
window.isLightStatusBarsCompat = resolveThemedBoolean(android.R.attr.windowLightStatusBar)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
window.isLightStatusBarsCompat =
resolveThemedBoolean(android.R.attr.windowLightStatusBar)
}
if (Build.VERSION.SDK_INT >= 27) {
window.isLightNavigationBarCompat = resolveThemedBoolean(android.R.attr.windowLightNavigationBar)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
window.isLightNavigationBarCompat =
resolveThemedBoolean(android.R.attr.windowLightNavigationBar)
}
this.dayNight = dayNight
}
enum class Event {
ServiceRecreated,
ActivityStart,
ActivityStop,
ClashStop,
ClashStart,
ProfileLoaded,
ProfileChanged,
ProfileUpdateCompleted,
ProfileUpdateFailed,
}
}

View file

@ -1,92 +0,0 @@
package com.github.kr328.clash
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import com.github.kr328.clash.common.constants.Intents
import com.github.kr328.clash.common.util.intent
import com.github.kr328.clash.common.util.setUUID
import com.github.kr328.clash.design.MainDesign
import com.github.kr328.clash.design.ui.ToastDuration
import com.github.kr328.clash.remote.Remote
import com.github.kr328.clash.remote.StatusClient
import com.github.kr328.clash.service.model.Profile
import com.github.kr328.clash.util.startClashService
import com.github.kr328.clash.util.stopClashService
import com.github.kr328.clash.util.withProfile
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import java.util.*
class ExternalControlActivity : Activity(), CoroutineScope by MainScope() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
when(intent.action) {
Intent.ACTION_VIEW -> {
val uri = intent.data ?: return finish()
val url = uri.getQueryParameter("url") ?: return finish()
launch {
val uuid = withProfile {
val type = when (uri.getQueryParameter("type")?.lowercase(Locale.getDefault())) {
"url" -> Profile.Type.Url
"file" -> Profile.Type.File
else -> Profile.Type.Url
}
val name = uri.getQueryParameter("name") ?: getString(R.string.new_profile)
create(type, name).also {
patch(it, name, url, 0)
}
}
startActivity(PropertiesActivity::class.intent.setUUID(uuid))
finish()
}
}
Intents.ACTION_TOGGLE_CLASH -> if(Remote.broadcasts.clashRunning) {
stopClash()
}
else {
startClash()
}
Intents.ACTION_START_CLASH -> if(!Remote.broadcasts.clashRunning) {
startClash()
}
else {
Toast.makeText(this, R.string.external_control_started, Toast.LENGTH_LONG).show()
}
Intents.ACTION_STOP_CLASH -> if(Remote.broadcasts.clashRunning) {
stopClash()
}
else {
Toast.makeText(this, R.string.external_control_stopped, Toast.LENGTH_LONG).show()
}
}
return finish()
}
private fun startClash() {
// if (currentProfile == null) {
// Toast.makeText(this, R.string.no_profile_selected, Toast.LENGTH_LONG).show()
// return
// }
val vpnRequest = startClashService()
if (vpnRequest != null) {
Toast.makeText(this, R.string.unable_to_start_vpn, Toast.LENGTH_LONG).show()
return
}
Toast.makeText(this, R.string.external_control_started, Toast.LENGTH_LONG).show()
}
private fun stopClash() {
stopClashService()
Toast.makeText(this, R.string.external_control_stopped, Toast.LENGTH_LONG).show()
}
}

View file

@ -0,0 +1,44 @@
package com.github.kr328.clash
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.github.kr328.clash.common.util.intent
import com.github.kr328.clash.common.util.setUUID
import com.github.kr328.clash.service.model.Profile
import com.github.kr328.clash.util.withProfile
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import java.util.*
class ExternalImportActivity : Activity(), CoroutineScope by MainScope() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (intent.action != Intent.ACTION_VIEW)
return finish()
val uri = intent.data ?: return finish()
val url = uri.getQueryParameter("url") ?: return finish()
launch {
val uuid = withProfile {
val type = when (uri.getQueryParameter("type")?.lowercase(Locale.getDefault())) {
"url" -> Profile.Type.Url
"file" -> Profile.Type.File
else -> Profile.Type.Url
}
val name = uri.getQueryParameter("name") ?: getString(R.string.new_profile)
create(type, name).also {
patch(it, name, url, 0)
}
}
startActivity(PropertiesActivity::class.intent.setUUID(uuid))
finish()
}
}
}

View file

@ -116,7 +116,7 @@ class FilesActivity : BaseActivity<FilesDesign>() {
}
is FilesDesign.Request.ExportFile -> {
val uri: Uri? = startActivityForResult(
ActivityResultContracts.CreateDocument("text/plain"),
ActivityResultContracts.CreateDocument(),
it.file.name
)
@ -141,7 +141,7 @@ class FilesActivity : BaseActivity<FilesDesign>() {
}
override fun onBackPressed() {
design?.requests?.trySend(FilesDesign.Request.PopStack)
design?.requests?.offer(FilesDesign.Request.PopStack)
}
private suspend fun FilesDesign.fetch(client: FilesClient, stack: Stack<String>, root: String) {

View file

@ -8,7 +8,6 @@ import android.os.IBinder
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import com.github.kr328.clash.common.compat.startForegroundServiceCompat
import com.github.kr328.clash.common.log.Log
import com.github.kr328.clash.common.util.fileName
import com.github.kr328.clash.common.util.intent
import com.github.kr328.clash.common.util.ticker
@ -48,7 +47,6 @@ class LogcatActivity : BaseActivity<LogcatDesign>() {
val messages = try {
LogcatReader(this, file).readAll()
} catch (e: Exception) {
Log.e("Fail to read log file ${file.fileName}: ${e.message}")
return showInvalid()
}
@ -69,7 +67,7 @@ class LogcatActivity : BaseActivity<LogcatDesign>() {
}
LogcatDesign.Request.Export -> {
val output = startActivityForResult(
ActivityResultContracts.CreateDocument("text/plain"),
ActivityResultContracts.CreateDocument(),
file.fileName
)

View file

@ -1,5 +1,7 @@
package com.github.kr328.clash
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.ComponentName
@ -7,21 +9,20 @@ import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.os.IInterface
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.github.kr328.clash.common.compat.getColorCompat
import com.github.kr328.clash.common.compat.pendingIntentFlags
import com.github.kr328.clash.common.log.Log
import com.github.kr328.clash.common.util.intent
import com.github.kr328.clash.core.model.LogMessage
import com.github.kr328.clash.log.LogcatCache
import com.github.kr328.clash.log.LogcatWriter
import com.github.kr328.clash.service.RemoteService
import com.github.kr328.clash.service.ClashManager
import com.github.kr328.clash.service.remote.IClashManager
import com.github.kr328.clash.service.remote.ILogObserver
import com.github.kr328.clash.service.remote.IRemoteService
import com.github.kr328.clash.service.remote.unwrap
import com.github.kr328.clash.util.logsDir
import kotlinx.coroutines.*
@ -51,7 +52,7 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
showNotification()
bindService(RemoteService::class.intent, connection, Context.BIND_AUTO_CREATE)
bindService(ClashManager::class.intent, connection, Context.BIND_AUTO_CREATE)
}
override fun onDestroy() {
@ -87,7 +88,7 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
return stopSelf()
launch(Dispatchers.IO) {
val service = binder.unwrap(IRemoteService::class).clash()
val service = binder.unwrap(IClashManager::class)
val channel = Channel<LogMessage>(CACHE_CAPACITY)
try {
@ -96,7 +97,7 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
LogcatWriter(this@LogcatService).use {
val observer = object : ILogObserver {
override fun newItem(log: LogMessage) {
channel.trySend(log)
channel.offer(log)
}
}
@ -125,12 +126,16 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
return
NotificationManagerCompat.from(this)
.createNotificationChannel(
NotificationChannelCompat.Builder(
NotificationChannel(
CHANNEL_ID,
NotificationManagerCompat.IMPORTANCE_DEFAULT
).setName(getString(R.string.clash_logcat)).build()
getString(R.string.clash_logcat),
NotificationManager.IMPORTANCE_DEFAULT
)
)
}
@ -147,7 +152,7 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
R.id.nf_logcat_status,
LogcatActivity::class.intent
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP),
pendingIntentFlags(PendingIntent.FLAG_UPDATE_CURRENT)
PendingIntent.FLAG_UPDATE_CURRENT
)
)
.build()

View file

@ -47,7 +47,7 @@ class LogsActivity : BaseActivity<LogsDesign>() {
deleteAllLogs()
}
events.trySend(Event.ActivityStart)
events.offer(Event.ActivityStart)
}
}
is LogsDesign.Request.OpenFile -> {

View file

@ -5,13 +5,14 @@ import com.github.kr328.clash.common.util.intent
import com.github.kr328.clash.common.util.ticker
import com.github.kr328.clash.design.MainDesign
import com.github.kr328.clash.design.ui.ToastDuration
import com.github.kr328.clash.store.TipsStore
import com.github.kr328.clash.util.startClashService
import com.github.kr328.clash.util.stopClashService
import com.github.kr328.clash.util.withClash
import com.github.kr328.clash.util.withProfile
import com.github.kr328.clash.core.bridge.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.withContext
import java.util.concurrent.TimeUnit
@ -22,6 +23,10 @@ class MainActivity : BaseActivity<MainDesign>() {
setContentDesign(design)
launch(Dispatchers.IO) {
showUpdatedTips(design)
}
design.fetch()
val ticker = ticker(TimeUnit.SECONDS.toMillis(1))
@ -70,6 +75,20 @@ class MainActivity : BaseActivity<MainDesign>() {
}
}
private suspend fun showUpdatedTips(design: MainDesign) {
val tips = TipsStore(this)
if (tips.primaryVersion != TipsStore.CURRENT_PRIMARY_VERSION) {
tips.primaryVersion = TipsStore.CURRENT_PRIMARY_VERSION
val pkg = packageManager.getPackageInfo(packageName, 0)
if (pkg.firstInstallTime != pkg.lastUpdateTime) {
design.showUpdatedTips()
}
}
}
private suspend fun MainDesign.fetch() {
setClashRunning(clashRunning)
@ -126,7 +145,7 @@ class MainActivity : BaseActivity<MainDesign>() {
private suspend fun queryAppVersionName(): String {
return withContext(Dispatchers.IO) {
packageManager.getPackageInfo(packageName, 0).versionName + "\n" + Bridge.nativeCoreVersion().replace("_", "-")
packageManager.getPackageInfo(packageName, 0).versionName
}
}
}

View file

@ -7,13 +7,9 @@ import com.github.kr328.clash.common.compat.currentProcessName
import com.github.kr328.clash.common.log.Log
import com.github.kr328.clash.remote.Remote
import com.github.kr328.clash.service.util.sendServiceRecreated
import com.github.kr328.clash.util.clashDir
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import com.microsoft.appcenter.AppCenter
import com.microsoft.appcenter.analytics.Analytics
import com.microsoft.appcenter.crashes.Crashes
@Suppress("unused")
class MainApplication : Application() {
@ -26,8 +22,16 @@ class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize AppCenter
if (BuildConfig.APP_CENTER_KEY != null && !BuildConfig.DEBUG) {
AppCenter.start(
this,
BuildConfig.APP_CENTER_KEY,
Analytics::class.java, Crashes::class.java
)
}
val processName = currentProcessName
extractGeoFiles()
Log.d("Process $processName started")
@ -37,33 +41,4 @@ class MainApplication : Application() {
sendServiceRecreated()
}
}
private fun extractGeoFiles() {
clashDir.mkdirs();
val geoipFile = File(clashDir, "geoip.metadb")
if(!geoipFile.exists()) {
FileOutputStream(geoipFile).use {
assets.open("geoip.metadb").copyTo(it);
}
}
val geositeFile = File(clashDir, "geosite.dat")
if(!geositeFile.exists()) {
FileOutputStream(geositeFile).use {
assets.open("geosite.dat").copyTo(it);
}
}
val ASNFile = File(clashDir, "ASN.mmdb")
if(!ASNFile.exists()) {
FileOutputStream(ASNFile).use {
assets.open("ASN.mmdb").copyTo(it);
}
}
}
fun finalize() {
Global.destroy()
}
}

View file

@ -1,136 +0,0 @@
package com.github.kr328.clash
import android.database.Cursor
import android.net.Uri
import android.provider.OpenableColumns
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.design.MetaFeatureSettingsDesign
import com.github.kr328.clash.util.clashDir
import com.github.kr328.clash.util.withClash
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
class MetaFeatureSettingsActivity : BaseActivity<MetaFeatureSettingsDesign>() {
override suspend fun main() {
val configuration = withClash { queryOverride(Clash.OverrideSlot.Persist) }
defer {
withClash {
patchOverride(Clash.OverrideSlot.Persist, configuration)
}
}
val design = MetaFeatureSettingsDesign(
this,
configuration
)
setContentDesign(design)
while (isActive) {
select<Unit> {
events.onReceive {
}
design.requests.onReceive {
when (it) {
MetaFeatureSettingsDesign.Request.ResetOverride -> {
if (design.requestResetConfirm()) {
defer {
withClash {
clearOverride(Clash.OverrideSlot.Persist)
}
}
finish()
}
}
MetaFeatureSettingsDesign.Request.ImportGeoIp -> {
val uri = startActivityForResult(
ActivityResultContracts.GetContent(),
"*/*")
importGeoFile(uri, MetaFeatureSettingsDesign.Request.ImportGeoIp)
}
MetaFeatureSettingsDesign.Request.ImportGeoSite -> {
val uri = startActivityForResult(
ActivityResultContracts.GetContent(),
"*/*")
importGeoFile(uri, MetaFeatureSettingsDesign.Request.ImportGeoSite)
}
MetaFeatureSettingsDesign.Request.ImportCountry -> {
val uri = startActivityForResult(
ActivityResultContracts.GetContent(),
"*/*")
importGeoFile(uri, MetaFeatureSettingsDesign.Request.ImportCountry)
}
MetaFeatureSettingsDesign.Request.ImportASN -> {
val uri = startActivityForResult(
ActivityResultContracts.GetContent(),
"*/*")
importGeoFile(uri, MetaFeatureSettingsDesign.Request.ImportASN)
}
}
}
}
}
}
private val validDatabaseExtensions = listOf(
".metadb", ".db", ".dat", ".mmdb"
)
private suspend fun importGeoFile(uri: Uri?, importType: MetaFeatureSettingsDesign.Request) {
val cursor: Cursor? = uri?.let {
contentResolver.query(it, null, null, null, null, null)
}
cursor?.use {
if (it.moveToFirst()) {
val columnIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val displayName: String =
if (columnIndex != -1) it.getString(columnIndex) else "";
val ext = "." + displayName.substringAfterLast(".")
if (!validDatabaseExtensions.contains(ext)) {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.geofile_unknown_db_format)
.setMessage(getString(R.string.geofile_unknown_db_format_message,
validDatabaseExtensions.joinToString("/")))
.setPositiveButton("OK") { _, _ -> }
.show()
return
}
val outputFileName = when (importType) {
MetaFeatureSettingsDesign.Request.ImportGeoIp ->
"geoip$ext"
MetaFeatureSettingsDesign.Request.ImportGeoSite ->
"geosite$ext"
MetaFeatureSettingsDesign.Request.ImportCountry ->
"country$ext"
MetaFeatureSettingsDesign.Request.ImportASN ->
"ASN$ext"
else -> ""
}
withContext(Dispatchers.IO) {
val outputFile = File(clashDir, outputFileName);
contentResolver.openInputStream(uri).use { ins ->
FileOutputStream(outputFile).use { outs ->
ins?.copyTo(outs)
}
}
}
Toast.makeText(this, getString(R.string.geofile_imported, displayName),
Toast.LENGTH_LONG).show()
return
}
}
Toast.makeText(this, R.string.geofile_import_failed, Toast.LENGTH_LONG).show()
}
}

View file

@ -45,14 +45,45 @@ class OverrideSettingsActivity : BaseActivity<OverrideSettingsDesign>() {
withClash {
clearOverride(Clash.OverrideSlot.Persist)
}
service.sideloadGeoip = ""
}
finish()
}
}
OverrideSettingsDesign.Request.EditSideloadGeoip -> {
withContext(Dispatchers.IO) {
val list = querySideloadProviders()
val initial = service.sideloadGeoip
val exist = list.any { info -> info.packageName == initial }
service.sideloadGeoip =
design.requestSelectSideload(if (exist) initial else "", list)
}
}
}
}
}
}
}
private fun querySideloadProviders(): List<AppInfo> {
val apps = packageManager.getInstalledPackages(PackageManager.GET_META_DATA)
.filter {
it.applicationInfo.metaData?.containsKey(Metadata.GEOIP_FILE_NAME)
?: false
}
.map { it.toAppInfo(packageManager) }
return listOf(
AppInfo(
packageName = "",
label = getString(R.string.use_built_in),
icon = getDrawableCompat(R.drawable.ic_baseline_work)!!,
installTime = 0,
updateDate = 0,
)
) + apps
}
}

View file

@ -1,23 +1,13 @@
package com.github.kr328.clash
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.github.kr328.clash.common.util.intent
import com.github.kr328.clash.common.util.setUUID
import com.github.kr328.clash.common.util.ticker
import com.github.kr328.clash.design.ProfilesDesign
import com.github.kr328.clash.design.ui.ToastDuration
import com.github.kr328.clash.R
import com.github.kr328.clash.service.model.Profile
import com.github.kr328.clash.util.withProfile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.withContext
import java.util.*
import java.util.concurrent.TimeUnit
class ProfilesActivity : BaseActivity<ProfilesDesign>() {
@ -44,16 +34,9 @@ class ProfilesActivity : BaseActivity<ProfilesDesign>() {
startActivity(NewProfileActivity::class.intent)
ProfilesDesign.Request.UpdateAll ->
withProfile {
try {
queryAll().forEach { p ->
if (p.imported && p.type != Profile.Type.File)
update(p.uuid)
}
}
finally {
withContext(Dispatchers.Main) {
design.finishUpdateAll();
}
queryAll().forEach { p ->
if (p.imported && p.type != Profile.Type.File)
update(p.uuid)
}
}
is ProfilesDesign.Request.Update ->
@ -91,37 +74,4 @@ class ProfilesActivity : BaseActivity<ProfilesDesign>() {
patchProfiles(queryAll())
}
}
override fun onProfileUpdateCompleted(uuid: UUID?) {
if(uuid == null)
return;
launch {
var name: String? = null;
withProfile {
name = queryByUUID(uuid)?.name
}
design?.showToast(
getString(R.string.toast_profile_updated_complete, name),
ToastDuration.Long
)
}
}
override fun onProfileUpdateFailed(uuid: UUID?, reason: String?) {
if(uuid == null)
return;
launch {
var name: String? = null;
withProfile {
name = queryByUUID(uuid)?.name
}
design?.showToast(
getString(R.string.toast_profile_updated_failed, name, reason),
ToastDuration.Long
){
setAction(R.string.edit) {
startActivity(PropertiesActivity::class.intent.setUUID(uuid))
}
}
}
}
}

View file

@ -5,12 +5,15 @@ import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.core.model.Proxy
import com.github.kr328.clash.design.ProxyDesign
import com.github.kr328.clash.design.model.ProxyState
import com.github.kr328.clash.store.TipsStore
import com.github.kr328.clash.util.withClash
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import java.util.concurrent.TimeUnit
class ProxyActivity : BaseActivity<ProxyDesign>() {
override suspend fun main() {
@ -19,6 +22,7 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
val states = List(names.size) { ProxyState("?") }
val unorderedStates = names.indices.map { names[it] to states[it] }.toMap()
val reloadLock = Semaphore(10)
val tips = TipsStore(this)
val design = ProxyDesign(
this,
@ -29,6 +33,17 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
setContentDesign(design)
launch(Dispatchers.IO) {
val pkg = packageManager.getPackageInfo(packageName, 0)
val validate = System.currentTimeMillis() - pkg.firstInstallTime > TimeUnit.DAYS.toMillis(5)
if (tips.requestDonate && validate) {
tips.requestDonate = false
design.requestDonate()
}
}
design.requests.send(ProxyDesign.Request.ReloadAll)
while (isActive) {
@ -58,7 +73,7 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
}
ProxyDesign.Request.ReloadAll -> {
names.indices.forEach { idx ->
design.requests.trySend(ProxyDesign.Request.Reload(idx))
design.requests.offer(ProxyDesign.Request.Reload(idx))
}
}
is ProxyDesign.Request.Reload -> {

View file

@ -24,8 +24,6 @@ class SettingsActivity : BaseActivity<SettingsDesign>() {
startActivity(NetworkSettingsActivity::class.intent)
SettingsDesign.Request.StartOverride ->
startActivity(OverrideSettingsActivity::class.intent)
SettingsDesign.Request.StartMetaFeature ->
startActivity(MetaFeatureSettingsActivity::class.intent)
}
}
}

View file

@ -16,28 +16,16 @@ class LogcatReader(context: Context, file: LogFile) : AutoCloseable {
}
fun readAll(): List<LogMessage> {
var lastTime = Date(0)
return reader.lineSequence()
.map { it.trim() }
.filter { !it.startsWith("#") }
.map { it.split(":", limit = 3) }
.map {
val time = it[0].toLongOrNull()?.let { Date(it) } ?: lastTime
val logMessage = if (it[0].toLongOrNull() != null) {
LogMessage(
time = time,
level = LogMessage.Level.valueOf(it[1]),
message = it[2]
)
} else {
LogMessage(
time = time,
level = LogMessage.Level.Warning, // or any default level
message = it.joinToString(":")
)
}
lastTime = time
logMessage
LogMessage(
time = Date(it[0].toLong()),
level = LogMessage.Level.valueOf(it[1]),
message = it[2]
)
}
.toList()
}

View file

@ -8,8 +8,7 @@ object SystemLogcat {
"Go",
"DEBUG",
"AndroidRuntime",
"ClashMetaForAndroid",
"LwIP",
"ClashForAndroid"
)
fun dumpCrash(): String {

View file

@ -7,7 +7,6 @@ import android.content.Intent
import android.content.IntentFilter
import com.github.kr328.clash.common.constants.Intents
import com.github.kr328.clash.common.log.Log
import java.util.*
class Broadcasts(private val context: Application) {
interface Observer {
@ -15,8 +14,6 @@ class Broadcasts(private val context: Application) {
fun onStarted()
fun onStopped(cause: String?)
fun onProfileChanged()
fun onProfileUpdateCompleted(uuid: UUID?)
fun onProfileUpdateFailed(uuid: UUID?, reason: String?)
fun onProfileLoaded()
}
@ -55,17 +52,6 @@ class Broadcasts(private val context: Application) {
receivers.forEach {
it.onProfileChanged()
}
Intents.ACTION_PROFILE_UPDATE_COMPLETED ->
receivers.forEach {
it.onProfileUpdateCompleted(
UUID.fromString(intent.getStringExtra(Intents.EXTRA_UUID)))
}
Intents.ACTION_PROFILE_UPDATE_FAILED ->
receivers.forEach {
it.onProfileUpdateFailed(
UUID.fromString(intent.getStringExtra(Intents.EXTRA_UUID)),
intent.getStringExtra(Intents.EXTRA_FAIL_REASON))
}
Intents.ACTION_PROFILE_LOADED -> {
receivers.forEach {
it.onProfileLoaded()
@ -93,8 +79,6 @@ class Broadcasts(private val context: Application) {
addAction(Intents.ACTION_CLASH_STARTED)
addAction(Intents.ACTION_CLASH_STOPPED)
addAction(Intents.ACTION_PROFILE_CHANGED)
addAction(Intents.ACTION_PROFILE_UPDATE_COMPLETED)
addAction(Intents.ACTION_PROFILE_UPDATE_FAILED)
addAction(Intents.ACTION_PROFILE_LOADED)
})

View file

@ -5,18 +5,18 @@ import android.content.Intent
import com.github.kr328.clash.ApkBrokenActivity
import com.github.kr328.clash.AppCrashedActivity
import com.github.kr328.clash.common.Global
import com.github.kr328.clash.common.log.Log
import com.github.kr328.clash.common.util.intent
import com.github.kr328.clash.store.AppStore
import com.github.kr328.clash.util.ApplicationObserver
import com.github.kr328.clash.util.verifyApk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
object Remote {
val broadcasts: Broadcasts = Broadcasts(Global.application)
val service: Service = Service(Global.application) {
val services: Services = Services(Global.application) {
ApplicationObserver.createdActivities.forEach { it.finish() }
val intent = AppCrashedActivity::class.intent
@ -30,25 +30,14 @@ object Remote {
fun launch() {
ApplicationObserver.attach(Global.application)
ApplicationObserver.onVisibleChanged {
if(it) {
Log.d("App becomes visible")
service.bind()
broadcasts.register()
}
else {
Log.d("App becomes invisible")
service.unbind()
broadcasts.unregister()
}
}
ApplicationObserver.onVisibleChanged(visible::offer)
Global.launch(Dispatchers.IO) {
verifyApp()
GlobalScope.launch(Dispatchers.IO) {
run()
}
}
private suspend fun verifyApp() {
private suspend fun run() {
val context = Global.application
val store = AppStore(context)
val updatedAt = getLastUpdated(context)
@ -65,6 +54,16 @@ object Remote {
store.updatedAt = updatedAt
}
}
while (true) {
if (visible.receive()) {
services.bind()
broadcasts.register()
} else {
services.unbind()
broadcasts.unregister()
}
}
}
private fun getLastUpdated(context: Context): Long {

View file

@ -1,59 +0,0 @@
package com.github.kr328.clash.remote
import android.app.Application
import android.content.ComponentName
import android.content.Context
import android.content.ServiceConnection
import android.os.IBinder
import com.github.kr328.clash.common.log.Log
import com.github.kr328.clash.common.util.intent
import com.github.kr328.clash.service.RemoteService
import com.github.kr328.clash.service.remote.IRemoteService
import com.github.kr328.clash.service.remote.unwrap
import com.github.kr328.clash.util.unbindServiceSilent
import java.util.concurrent.TimeUnit
class Service(private val context: Application, val crashed: () -> Unit) {
val remote = Resource<IRemoteService>()
private val connection = object : ServiceConnection {
private var lastCrashed: Long = -1
override fun onServiceConnected(name: ComponentName?, service: IBinder) {
remote.set(service.unwrap(IRemoteService::class))
}
override fun onServiceDisconnected(name: ComponentName?) {
remote.set(null)
if (System.currentTimeMillis() - lastCrashed < TOGGLE_CRASHED_INTERVAL) {
unbind()
crashed()
}
lastCrashed = System.currentTimeMillis()
Log.w("RemoteService killed or crashed")
}
}
fun bind() {
try {
context.bindService(RemoteService::class.intent, connection, Context.BIND_AUTO_CREATE)
} catch (e: Exception) {
unbind()
crashed()
}
}
fun unbind() {
context.unbindServiceSilent(connection)
remote.set(null)
}
companion object {
private val TOGGLE_CRASHED_INTERVAL = TimeUnit.SECONDS.toMillis(10)
}
}

View file

@ -0,0 +1,88 @@
package com.github.kr328.clash.remote
import android.app.Application
import android.content.ComponentName
import android.content.Context
import android.content.ServiceConnection
import android.os.IBinder
import com.github.kr328.clash.common.log.Log
import com.github.kr328.clash.common.util.intent
import com.github.kr328.clash.service.ClashManager
import com.github.kr328.clash.service.ProfileService
import com.github.kr328.clash.service.remote.IClashManager
import com.github.kr328.clash.service.remote.IProfileManager
import com.github.kr328.clash.service.remote.unwrap
import com.github.kr328.clash.util.unbindServiceSilent
import java.util.concurrent.TimeUnit
class Services(private val context: Application, val crashed: () -> Unit) {
val clash = Resource<IClashManager>()
val profile = Resource<IProfileManager>()
private val clashConnection = object : ServiceConnection {
private var lastCrashed: Long = -1
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
clash.set(service?.unwrap(IClashManager::class))
}
override fun onServiceDisconnected(name: ComponentName?) {
clash.set(null)
if (System.currentTimeMillis() - lastCrashed < TOGGLE_CRASHED_INTERVAL) {
unbind()
crashed()
}
lastCrashed = System.currentTimeMillis()
Log.w("ClashManager crashed")
}
}
private val profileConnection = object : ServiceConnection {
private var lastCrashed: Long = -1
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
profile.set(service?.unwrap(IProfileManager::class))
}
override fun onServiceDisconnected(name: ComponentName?) {
profile.set(null)
if (System.currentTimeMillis() - lastCrashed < TOGGLE_CRASHED_INTERVAL) {
unbind()
crashed()
}
lastCrashed = System.currentTimeMillis()
Log.w("ProfileService crashed")
}
}
fun bind() {
try {
context.bindService(ClashManager::class.intent, clashConnection, Context.BIND_AUTO_CREATE)
context.bindService(ProfileService::class.intent, profileConnection, Context.BIND_AUTO_CREATE)
} catch (e: Exception) {
unbind()
crashed()
}
}
fun unbind() {
context.unbindServiceSilent(clashConnection)
context.unbindServiceSilent(profileConnection)
clash.set(null)
profile.set(null)
}
companion object {
private val TOGGLE_CRASHED_INTERVAL = TimeUnit.SECONDS.toMillis(10)
}
}

View file

@ -9,8 +9,7 @@ import java.io.File
import java.util.zip.ZipFile
object ApplicationObserver {
private val _createdActivities: MutableSet<Activity> = mutableSetOf()
private val _visibleActivities: MutableSet<Activity> = mutableSetOf()
private val activities: MutableSet<Activity> = mutableSetOf()
private var visibleChanged: (Boolean) -> Unit = {}
@ -24,31 +23,25 @@ object ApplicationObserver {
}
val createdActivities: Set<Activity>
get() = _createdActivities
get() = activities
private val activityObserver = object : Application.ActivityLifecycleCallbacks {
@Synchronized
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
_createdActivities.add(activity)
activities.add(activity)
appVisible = true
}
@Synchronized
override fun onActivityDestroyed(activity: Activity) {
_createdActivities.remove(activity)
_visibleActivities.remove(activity)
appVisible = _visibleActivities.isNotEmpty()
}
override fun onActivityStarted(activity: Activity) {
_visibleActivities.add(activity)
appVisible = true
}
override fun onActivityStopped(activity: Activity) {
_visibleActivities.remove(activity)
appVisible = _visibleActivities.isNotEmpty()
activities.remove(activity)
appVisible = activities.isNotEmpty()
}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}

View file

@ -4,7 +4,4 @@ import android.content.Context
import java.io.File
val Context.logsDir: File
get() = cacheDir.resolve("logs")
val Context.clashDir: File
get() = filesDir.resolve("clash")
get() = cacheDir.resolve("logs")

View file

@ -14,15 +14,14 @@ suspend fun <T> withClash(
block: suspend IClashManager.() -> T
): T {
while (true) {
val remote = Remote.service.remote.get()
val client = remote.clash()
val client = Remote.services.clash.get()
try {
return withContext(context) { client.block() }
} catch (e: DeadObjectException) {
Log.w("Remote services panic")
Remote.service.remote.reset(remote)
Remote.services.clash.reset(client)
}
}
}
@ -32,15 +31,14 @@ suspend fun <T> withProfile(
block: suspend IProfileManager.() -> T
): T {
while (true) {
val remote = Remote.service.remote.get()
val client = remote.profile()
val client = Remote.services.profile.get()
try {
return withContext(context) { client.block() }
} catch (e: DeadObjectException) {
Log.w("Remote services panic")
Remote.service.remote.reset(remote)
Remote.services.profile.reset(client)
}
}
}

View file

@ -2,15 +2,14 @@
xmlns:tools="http://schemas.android.com/tools"
android:width="108dp"
android:height="108dp"
android:viewportWidth="403"
android:viewportHeight="403"
android:name="vector">
<group>
android:viewportWidth="406.92642"
android:viewportHeight="406.92642">
<group
android:translateX="103.4632"
android:translateY="103.4632">
<path
android:pathData="M 141.08 128.47 C 141.88 128.42 142.72 128.38 143.41 128.8 C 144.51 129.46 145.39 130.41 146.33 131.25 C 160.38 144.1 174.48 156.9 188.53 169.75 C 189.48 170.59 190.37 171.56 191.54 172.11 C 192.45 172.55 193.49 172.33 194.46 172.2 C 197.04 171.76 199.66 171.74 202.26 171.74 C 204.77 171.78 207.29 171.86 209.77 172.27 C 210.77 172.38 211.95 172.71 212.71 171.84 C 228.19 157.7 243.73 143.58 259.23 129.43 C 260.54 128.06 262.55 128.36 264.26 128.57 C 268.58 129.21 272.94 129.51 277.28 130 C 278 130.14 279.02 130.19 279.27 131.05 C 279.61 132.54 279.42 134.07 279.46 135.58 C 279.44 166.17 279.44 196.77 279.42 227.36 C 279.41 228.92 279.51 230.47 279.35 232.01 C 279.29 232.56 279.22 233.21 278.76 233.59 C 277.8 234.01 276.74 234.17 275.72 234.42 C 271.39 235.43 266.98 236.16 262.66 237.26 C 260.49 237.83 258.25 237.61 256.04 237.64 C 254.91 237.63 253.74 237.73 252.61 237.54 C 251.95 237.49 251.48 236.9 251.43 236.26 C 251.19 234.74 251.33 233.2 251.31 231.67 C 251.29 216.61 251.28 201.54 251.29 186.48 C 251.27 185 251.38 183.49 251.17 182.01 C 250.9 180.3 249.11 179.1 247.44 179.31 C 246.51 179.32 245.72 179.9 245.05 180.49 C 237.57 187.38 230 194.15 222.54 201.02 C 221.68 201.76 220.7 202.63 219.48 202.51 C 217.88 202.37 216.37 201.8 214.82 201.44 C 208.61 199.89 202.12 199.7 195.77 200.24 C 192.68 200.67 189.56 201.02 186.59 202 C 185.63 202.27 184.66 202.57 183.67 202.52 C 182.65 202.45 181.86 201.78 181.14 201.15 C 173.72 194.33 166.23 187.63 158.81 180.81 C 158.09 180.18 157.31 179.49 156.33 179.34 C 155.1 179.19 153.83 179.69 152.99 180.59 C 152.26 181.42 152.23 182.56 152.19 183.61 C 152.23 200.72 152.18 217.81 152.21 234.91 C 152.18 235.57 152.18 236.24 151.92 236.87 C 151.53 237.58 150.62 237.61 149.91 237.64 C 147.99 237.68 146.08 237.63 144.16 237.64 C 141.99 237.68 139.9 237.05 137.81 236.62 C 133.97 235.77 130.14 234.91 126.29 234.1 C 125.48 233.9 124.35 233.66 124.2 232.66 C 123.96 230.91 124.13 229.14 124.1 227.36 C 124.06 196.88 124.08 166.41 124.08 135.93 C 124.08 134.49 124 133.06 124.13 131.64 C 124.18 131.12 124.33 130.51 124.84 130.24 C 125.38 130 125.99 129.97 126.56 129.88 C 131.42 129.55 136.25 128.96 141.08 128.47 Z M 198.81 240.82 C 200.99 240.64 203.19 240.7 205.35 240.82 C 207.04 240.8 208.08 242.85 207.31 244.28 C 206.36 246.17 205.38 248.03 204.27 249.84 C 203.56 251.14 201.5 251.39 200.57 250.19 C 199.67 249.09 199.15 247.78 198.44 246.56 C 197.9 245.55 197.21 244.59 196.96 243.44 C 196.7 242.24 197.62 240.99 198.81 240.82 Z"
android:fillColor="#3372b6"/>
<path
android:pathData="M 125.67 244.75 C 126.09 244.7 126.51 244.67 126.95 244.67 C 139.02 244.7 151.09 244.69 163.19 244.69 C 164.26 244.67 165.5 244.94 166.14 245.92 C 166.8 247.02 166.66 248.72 165.52 249.45 C 164.74 249.99 163.74 249.99 162.83 250.02 C 150.88 249.99 138.9 250.01 126.95 250.02 C 126.28 249.99 125.6 249.99 124.97 249.77 C 123.94 249.38 123.25 248.25 123.4 247.15 C 123.49 245.97 124.43 244.86 125.67 244.76 L 125.67 244.75 Z M 239.67 244.75 C 241.29 244.59 242.93 244.72 244.57 244.69 C 255.33 244.67 266.1 244.72 276.86 244.67 C 277.55 244.7 278.27 244.69 278.92 244.97 C 279.88 245.4 280.42 246.44 280.42 247.47 C 280.44 248.67 279.44 249.8 278.24 249.92 C 276.7 250.11 275.15 249.99 273.61 250 L 243.85 250 C 242.33 249.99 240.79 250.12 239.29 249.87 C 238.15 249.67 237.23 248.64 237.31 247.46 C 237.16 246.07 238.34 244.85 239.67 244.75 Z M 162.61 257.67 C 163.41 257.45 164.3 257.33 165.06 257.72 C 166.65 258.45 167.02 260.98 165.6 262.08 C 164.76 262.67 163.74 262.91 162.8 263.24 C 159.22 264.36 155.74 265.76 152.16 266.87 C 148.04 268.17 144 269.71 139.9 271.09 C 136.91 272.06 133.94 273.07 130.97 274.1 C 129.73 274.52 128.54 275.06 127.27 275.33 C 126.49 275.5 125.63 275.35 125.02 274.83 C 124.01 274.03 123.82 272.5 124.4 271.4 C 124.87 270.6 125.82 270.32 126.63 270 C 129.2 269.1 131.73 268.17 134.31 267.36 C 140.78 265.27 147.14 262.84 153.63 260.8 C 156.65 259.81 159.59 258.62 162.61 257.67 Z M 239.3 257.54 C 240.23 257.32 241.14 257.67 242.02 257.92 C 244.84 258.89 247.61 259.97 250.45 260.88 C 257.12 262.99 263.65 265.51 270.34 267.62 C 272.53 268.33 274.71 269.14 276.91 269.9 C 277.73 270.22 278.64 270.49 279.25 271.18 C 280.21 272.5 279.64 274.64 278.1 275.23 C 277.11 275.62 276.06 275.23 275.1 274.93 C 271.13 273.46 267.11 272.14 263.09 270.83 C 259.18 269.47 255.29 268.04 251.36 266.79 C 247.78 265.66 244.32 264.26 240.74 263.16 C 239.79 262.82 238.78 262.59 238.02 261.91 C 236.65 260.58 237.39 257.88 239.3 257.54 Z"
android:fillColor="#f39800"/>
android:fillColor="#1E4376"
android:pathData="M47.211,168.128C70.531,-34.962 67.471,13.788 94.071,43.818c13.45,-1.52 27.24,-3.47 40.82,-0.67c2.64,0.13 5.42,1.86 7.71,0.18c4.12,-6.27 7.35,-13.54 11.35,-20c12.19,-24.44 12.85,19.54 15.48,26.52c5.23,32.99 10.89,64.46 14.67,97.59c0.31,10.72 5.74,32.92 1.08,33.56c-49.36,5.23 -147.71,3.91 -160.84,-6.3c-15.85,-10.5 -15.18,-35.33 2.03,-43.72c3.63,-2.03 10.68,-3.72 11.94,0.7c-2.41,4.99 -8.79,5.77 -12.12,11.17C16.621,158.948 33.111,168.888 47.211,168.128zM87.841,74.008c-10.42,0.52 -9.59,14.89 -0.07,15.18C98.191,88.668 97.361,74.298 87.841,74.008zM149.121,89.188c10.46,-0.34 9.85,-14.71 0.38,-15.18C139.031,74.348 139.651,88.718 149.121,89.188zM107.871,99.228c2.16,3.48 5.28,3.29 9.79,0.16c3.81,3.17 8.06,3.28 9.18,-0.19c-3.78,1.17 -7.04,0.79 -9.4,-3.49C115.371,100.108 112.071,100.428 107.871,99.228z"
tools:ignore="VectorPath" />
</group>
</vector>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@color/color_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@color/color_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_banner_background">#FFFFFF</color>
</resources>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View file

@ -1,165 +1,12 @@
@file:Suppress("UNUSED_VARIABLE")
import com.android.build.gradle.AppExtension
import com.android.build.gradle.BaseExtension
import java.net.URL
import java.util.*
buildscript {
allprojects {
repositories {
mavenCentral()
google()
maven("https://raw.githubusercontent.com/MetaCubeX/maven-backup/main/releases")
}
dependencies {
classpath(libs.build.android)
classpath(libs.build.kotlin.common)
classpath(libs.build.kotlin.serialization)
classpath(libs.build.ksp)
classpath(libs.build.golang)
}
}
subprojects {
repositories {
mavenCentral()
google()
maven("https://raw.githubusercontent.com/MetaCubeX/maven-backup/main/releases")
}
val isApp = name == "app"
apply(plugin = if (isApp) "com.android.application" else "com.android.library")
extensions.configure<BaseExtension> {
defaultConfig {
if (isApp) {
applicationId = "com.github.metacubex.clash"
}
minSdk = 21
targetSdk = 31
versionName = "2.11.0"
versionCode = 211000
resValue("string", "release_name", "v$versionName")
resValue("integer", "release_code", "$versionCode")
externalNativeBuild {
cmake {
abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
}
}
if (!isApp) {
consumerProguardFiles("consumer-rules.pro")
} else {
setProperty("archivesBaseName", "cmfa-$versionName")
}
}
ndkVersion = "23.0.7599858"
compileSdkVersion(defaultConfig.targetSdk!!)
if (isApp) {
packagingOptions {
resources {
excludes.add("DebugProbesKt.bin")
}
}
}
productFlavors {
flavorDimensions("feature")
create("alpha") {
isDefault = true
dimension = flavorDimensionList[0]
versionNameSuffix = ".Alpha"
buildConfigField("boolean", "PREMIUM", "Boolean.parseBoolean(\"false\")")
resValue("string", "launch_name", "@string/launch_name_alpha")
resValue("string", "application_name", "@string/application_name_alpha")
if (isApp) {
applicationIdSuffix = ".alpha"
}
}
create("meta") {
dimension = flavorDimensionList[0]
versionNameSuffix = ".Meta"
buildConfigField("boolean", "PREMIUM", "Boolean.parseBoolean(\"false\")")
resValue("string", "launch_name", "@string/launch_name_meta")
resValue("string", "application_name", "@string/application_name_meta")
if (isApp) {
applicationIdSuffix = ".meta"
}
}
}
sourceSets {
getByName("meta") {
java.srcDirs("src/foss/java")
}
getByName("alpha") {
java.srcDirs("src/foss/java")
}
}
signingConfigs {
val keystore = rootProject.file("signing.properties")
if (keystore.exists()) {
create("release") {
val prop = Properties().apply {
keystore.inputStream().use(this::load)
}
storeFile = rootProject.file("release.keystore")
storePassword = prop.getProperty("keystore.password")!!
keyAlias = prop.getProperty("key.alias")!!
keyPassword = prop.getProperty("key.password")!!
}
}
}
buildTypes {
named("release") {
isMinifyEnabled = isApp
isShrinkResources = isApp
signingConfig = signingConfigs.findByName("release")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
named("debug") {
versionNameSuffix = ".debug"
}
}
buildFeatures.apply {
dataBinding {
isEnabled = name != "hideapi"
}
}
if (isApp) {
this as AppExtension
splits {
abi {
isEnable = true
isUniversalApk = true
}
}
maven {
url = uri("https://maven.kr328.app")
}
}
}
@ -167,15 +14,3 @@ subprojects {
task("clean", type = Delete::class) {
delete(rootProject.buildDir)
}
tasks.wrapper {
distributionType = Wrapper.DistributionType.ALL
doLast {
val sha256 = URL("$distributionUrl.sha256").openStream()
.use { it.reader().readText().trim() }
file("gradle/wrapper/gradle-wrapper.properties")
.appendText("distributionSha256Sum=$sha256")
}
}

35
buildSrc/build.gradle.kts Normal file
View file

@ -0,0 +1,35 @@
plugins {
kotlin("jvm") version "1.5.0"
`java-gradle-plugin`
}
repositories {
mavenCentral()
google()
}
dependencies {
implementation(kotlin("stdlib"))
compileOnly(gradleApi())
api(kotlin("gradle-plugin"))
api(kotlin("serialization"))
api("com.android.tools.build:gradle:4.2.1") {
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk7")
exclude("org.jetbrains.kotlin", "kotlin-reflect")
}
api("com.google.devtools.ksp:symbol-processing-gradle-plugin:1.5.0-1.0.0-alpha10") {
exclude("com.android.tools.build", "gradle")
}
}
gradlePlugin {
plugins {
create("golang") {
id = "library-golang"
implementationClass = "LibraryGolangPlugin"
}
}
}

View file

@ -0,0 +1,17 @@
import org.gradle.api.Project
const val buildVersionCode = 203022
const val buildVersionName = "2.3.22"
const val buildMinSdkVersion = 21
const val buildTargetSdkVersion = 30
const val buildNdkVersion = "23.0.7123448"
val Project.buildFlavor: String
get() {
return if (project(":core").file("src/main/golang/clash/script/script.go").exists())
"premium"
else
"open"
}

View file

@ -0,0 +1,14 @@
const val activityVersion = "1.2.3"
const val coroutineVersion = "1.4.3"
const val roomVersion = "2.3.0"
const val ktxVersion = "1.3.2"
const val appcompatVersion = "1.2.0"
const val muiltprocessVersion = "1.0.0"
const val kaidlVersion = "1.11"
const val appcenterVersion = "4.1.1"
const val serializationVersion = "1.2.1"
const val materialVersion = "1.3.0"
const val coordinatorlayoutVersion = "1.1.0"
const val recyclerviewVersion = "1.2.0"
const val fragmentVersion = "1.3.3"
const val viewpagerVersion = "1.0.0"

View file

@ -0,0 +1,8 @@
import org.gradle.api.Project
import java.io.File
val Project.golangSource: File
get() = file("src/main/golang")
val Project.golangBuild: File
get() = buildDir.resolve("intermediates/golang")

View file

@ -0,0 +1,119 @@
import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.DefaultTask
import org.gradle.api.GradleScriptException
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
abstract class GolangBuildTask : DefaultTask() {
abstract val debug: Property<Boolean>
@Input get
abstract val premium: Property<Boolean>
@Input get
abstract val nativeAbis: SetProperty<String>
@Input get
abstract val minSdkVersion: Property<Int>
@Input get
abstract val cCompilerBasePath: DirectoryProperty
@InputDirectory get
abstract val inputDirectory: DirectoryProperty
@InputDirectory get
abstract val outputDirectory: DirectoryProperty
@OutputDirectory get
@TaskAction
fun build() {
val src = inputDirectory.get().asFile
val cmd = if (debug.get()) {
"""
go build --buildmode=c-shared -trimpath -o "%s" -tags "without_gvisor,without_system,debug${if (premium.get()) ",premium" else ""}"
""".trimIndent().trim()
} else {
"""
go build --buildmode=c-shared -trimpath -o "%s" -tags "without_gvisor,without_system${if (premium.get()) ",premium" else ""}" -ldflags "-s -w"
""".trimIndent().trim()
}
nativeAbis.get().parallelStream().forEach {
val out = outputDirectory.get().file("$it/libclash.so")
cmd.format(out).exec(pwd = src, env = generateGolangBuildEnvironment(it))
}
}
private fun generateGolangBuildEnvironment(abi: String): Map<String, String> {
val (goArch, goArm) = when (abi) {
"arm64-v8a" -> "arm64" to ""
"armeabi-v7a" -> "arm" to "7"
"x86" -> "386" to ""
"x86_64" -> "amd64" to ""
else -> throw UnsupportedOperationException("unsupported abi: $abi")
}
val compiler = when (abi) {
"armeabi-v7a" ->
"armv7a-linux-androideabi${minSdkVersion.get()}-clang"
"arm64-v8a" ->
"aarch64-linux-android${minSdkVersion.get()}-clang"
"x86" ->
"i686-linux-android${minSdkVersion.get()}-clang"
"x86_64" ->
"x86_64-linux-android${minSdkVersion.get()}-clang"
else ->
throw GradleScriptException(
"Unsupported abi $abi",
FileNotFoundException("Unsupported abi $abi")
)
}
return mapOf(
"CC" to cCompilerBasePath.get().asFile.resolve(compiler).absolutePath,
"GOOS" to "android",
"GOARCH" to goArch,
"GOARM" to goArm,
"CGO_ENABLED" to "1",
"CFLAGS" to "-O3 -Werror",
)
}
private fun String.exec(
pwd: File,
env: Map<String, String> = System.getenv()
): String {
val process = ProcessBuilder().run {
if (Os.isFamily(Os.FAMILY_WINDOWS))
command("cmd.exe", "/c", this@exec)
else
command("bash", "-c", this@exec)
environment().putAll(env)
directory(pwd)
redirectErrorStream(true)
start()
}
val outputStream = ByteArrayOutputStream()
process.inputStream.copyTo(outputStream)
if (process.waitFor() != 0) {
println(outputStream.toString("utf-8"))
throw GradleScriptException("Exec $this failure", IOException())
}
return outputStream.toString("utf-8")
}
}

View file

@ -0,0 +1,69 @@
import com.android.build.gradle.LibraryExtension
import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.GradleScriptException
import org.gradle.api.Plugin
import org.gradle.api.Project
import java.io.File
import java.io.FileNotFoundException
import java.util.*
class LibraryGolangPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.extensions.getByType(LibraryExtension::class.java).apply {
target.afterEvaluate {
libraryVariants.forEach { variant ->
val abis = defaultConfig.externalNativeBuild.cmake.abiFilters +
defaultConfig.externalNativeBuild.ndkBuild.abiFilters
val nameCapitalize = variant.name.capitalize(Locale.getDefault())
val golangBuildDir = target.golangBuild.resolve(variant.name)
val task = target.tasks.register(
"externalGolangBuild$nameCapitalize",
GolangBuildTask::class.java
) {
it.premium.set(variant.flavorName == "premium")
it.debug.set(variant.name == "debug")
it.nativeAbis.set(abis)
it.minSdkVersion.set(defaultConfig.minSdk!!)
it.cCompilerBasePath.set(compilerBasePath)
it.inputDirectory.set(target.golangSource)
it.outputDirectory.set(golangBuildDir)
}
sourceSets.named(variant.name) {
it.jniLibs {
srcDir(golangBuildDir)
}
}
variant.externalNativeBuildProviders.forEach {
it.get().dependsOn(task)
}
target.tasks.filter { it.name.startsWith("buildCMake") }.forEach {
it.mustRunAfter(task)
}
}
}
}
}
private val LibraryExtension.compilerBasePath: File
get() {
val host = when {
Os.isFamily(Os.FAMILY_WINDOWS) ->
"windows"
Os.isFamily(Os.FAMILY_MAC) ->
"darwin"
Os.isFamily(Os.FAMILY_UNIX) ->
"linux"
else ->
throw GradleScriptException(
"Unsupported host",
FileNotFoundException("Unsupported host")
)
}
return ndkDirectory.resolve("toolchains/llvm/prebuilt/$host-x86_64/bin")
}
}

View file

@ -1,11 +1,47 @@
plugins {
kotlin("android")
id("com.android.library")
kotlin("android")
}
android {
compileSdk = buildTargetSdkVersion
defaultConfig {
minSdk = buildMinSdkVersion
targetSdk = buildTargetSdkVersion
versionCode = buildVersionCode
versionName = buildVersionName
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
named("release") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
compileOnly(project(":hideapi"))
implementation(libs.kotlin.coroutine)
implementation(libs.androidx.core)
implementation(kotlin("stdlib-jdk7"))
implementation("androidx.core:core-ktx:$ktxVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion")
}
repositories {
mavenCentral()
google()
}

View file

@ -1,11 +1,8 @@
package com.github.kr328.clash.common
import android.app.Application
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
object Global : CoroutineScope by CoroutineScope(Dispatchers.IO) {
object Global {
val application: Application
get() = application_
@ -14,8 +11,4 @@ object Global : CoroutineScope by CoroutineScope(Dispatchers.IO) {
fun init(application: Application) {
this.application_ = application
}
fun destroy() {
cancel()
}
}

View file

@ -2,8 +2,6 @@ package com.github.kr328.clash.common.compat
import android.app.ActivityThread
import android.app.Application
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import com.github.kr328.clash.common.log.Log
@ -20,12 +18,3 @@ val Application.currentProcessName: String
packageName
}
}
fun Drawable.foreground(): Drawable {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
this is AdaptiveIconDrawable && this.background == null
) {
return this.foreground
}
return this
}

View file

@ -4,12 +4,17 @@ package com.github.kr328.clash.common.compat
import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Build
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
fun Context.getColorCompat(@ColorRes id: Int): Int {
return ContextCompat.getColor(this, id)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
this.getColor(id)
} else {
resources.getColor(id)
}
}
fun Context.getDrawableCompat(@DrawableRes id: Int): Drawable? {

View file

@ -7,7 +7,7 @@ import android.text.Html
import android.text.Spanned
fun fromHtmlCompat(content: String): Spanned {
return if (Build.VERSION.SDK_INT >= 24) {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.fromHtml(content, Html.FROM_HTML_MODE_COMPACT)
} else {
Html.fromHtml(content)

View file

@ -3,13 +3,9 @@ package com.github.kr328.clash.common.compat
import android.app.PendingIntent
import android.os.Build
fun pendingIntentFlags(flags: Int, mutable: Boolean = false): Int {
return if (Build.VERSION.SDK_INT >= 24) {
if (Build.VERSION.SDK_INT > 30 && mutable) {
flags or PendingIntent.FLAG_MUTABLE
} else {
flags or PendingIntent.FLAG_IMMUTABLE
}
fun pendingIntentFlags(flags: Int, immutable: Boolean = false): Int {
return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M && immutable) {
flags or PendingIntent.FLAG_IMMUTABLE
} else {
flags
}

View file

@ -6,7 +6,7 @@ import android.content.pm.PackageInfo
val PackageInfo.versionCodeCompat: Long
get() {
return if (android.os.Build.VERSION.SDK_INT >= 28) {
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
longVersionCode
} else {
versionCode.toLong()

View file

@ -8,7 +8,7 @@ import java.util.*
val Configuration.preferredLocale: Locale
get() {
return if (Build.VERSION.SDK_INT >= 24) {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
locales[0]
} else {
locale

View file

@ -5,7 +5,7 @@ import android.content.Intent
import android.os.Build
fun Context.startForegroundServiceCompat(intent: Intent) {
if (Build.VERSION.SDK_INT >= 26) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)

View file

@ -4,8 +4,6 @@ import android.content.ComponentName
import com.github.kr328.clash.common.util.packageName
object Components {
private const val componentsPackageName = "com.github.kr328.clash"
val MAIN_ACTIVITY = ComponentName(packageName, "$componentsPackageName.MainActivity")
val PROPERTIES_ACTIVITY = ComponentName(packageName, "$componentsPackageName.PropertiesActivity")
val MAIN_ACTIVITY = ComponentName(packageName, "$packageName.MainActivity")
val PROPERTIES_ACTIVITY = ComponentName(packageName, "$packageName.PropertiesActivity")
}

View file

@ -5,9 +5,6 @@ import com.github.kr328.clash.common.util.packageName
object Intents {
// Public
val ACTION_PROVIDE_URL = "$packageName.action.PROVIDE_URL"
val ACTION_START_CLASH = "$packageName.action.START_CLASH"
val ACTION_STOP_CLASH = "$packageName.action.STOP_CLASH"
val ACTION_TOGGLE_CLASH = "$packageName.action.TOGGLE_CLASH"
const val EXTRA_NAME = "name"
@ -17,8 +14,6 @@ object Intents {
val ACTION_CLASH_STOPPED = "$packageName.intent.action.CLASH_STOPPED"
val ACTION_CLASH_REQUEST_STOP = "$packageName.intent.action.CLASH_REQUEST_STOP"
val ACTION_PROFILE_CHANGED = "$packageName.intent.action.PROFILE_CHANGED"
val ACTION_PROFILE_UPDATE_COMPLETED = "$packageName.intent.action.PROFILE_UPDATE_COMPLETED"
val ACTION_PROFILE_UPDATE_FAILED = "$packageName.intent.action.PROFILE_UPDATE_FAILED"
val ACTION_PROFILE_REQUEST_UPDATE = "$packageName.intent.action.REQUEST_UPDATE"
val ACTION_PROFILE_SCHEDULE_UPDATES = "$packageName.intent.action.SCHEDULE_UPDATES"
val ACTION_PROFILE_LOADED = "$packageName.intent.action.PROFILE_LOADED"
@ -26,5 +21,4 @@ object Intents {
const val EXTRA_STOP_REASON = "stop_reason"
const val EXTRA_UUID = "uuid"
const val EXTRA_FAIL_REASON = "fail_reason"
}

View file

@ -1,7 +1,7 @@
package com.github.kr328.clash.common.log
object Log {
private const val TAG = "ClashMetaForAndroid"
private const val TAG = "ClashForAndroid"
fun i(message: String, throwable: Throwable? = null) =
android.util.Log.i(TAG, message, throwable)

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="receive_clash_broadcasts">Получать оповещения от Clash</string>
<string name="receive_broadcasts_of_clash">Получать оповещения от сервисов Clash</string>
</resources>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="receive_clash_broadcasts">接收 Clash 廣播</string>
<string name="receive_broadcasts_of_clash">接收來自 Clash 內部的廣播</string>
</resources>

View file

@ -1,48 +1,72 @@
import com.github.kr328.golang.GolangBuildTask
import com.github.kr328.golang.GolangPlugin
import java.io.FileOutputStream
import java.net.URL
import java.time.Duration
plugins {
kotlin("android")
id("com.android.library")
kotlin("android")
id("kotlinx-serialization")
id("golang-android")
id("library-golang")
}
val golangSource = file("src/main/golang/native")
golang {
sourceSets {
create("alpha") {
tags.set(listOf("foss","with_gvisor","cmfa"))
srcDir.set(file("src/foss/golang"))
}
create("meta") {
tags.set(listOf("foss","with_gvisor","cmfa"))
srcDir.set(file("src/foss/golang"))
}
all {
fileName.set("libclash.so")
packageName.set("cfa/native")
}
}
}
val geoipDatabaseUrl =
"https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb"
val geoipInvalidate = Duration.ofDays(7)!!
val geoipOutput = buildDir.resolve("intermediates/golang_blob")
android {
productFlavors {
all {
externalNativeBuild {
cmake {
arguments("-DGO_SOURCE:STRING=${golangSource}")
arguments("-DGO_OUTPUT:STRING=${GolangPlugin.outputDirOf(project, null, null)}")
arguments("-DFLAVOR_NAME:STRING=$name")
}
compileSdk = buildTargetSdkVersion
ndkVersion = buildNdkVersion
flavorDimensions(buildFlavor)
defaultConfig {
minSdk = buildMinSdkVersion
targetSdk = buildTargetSdkVersion
versionCode = buildVersionCode
versionName = buildVersionName
consumerProguardFiles("consumer-rules.pro")
externalNativeBuild {
cmake {
abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
arguments(
"-DGO_SOURCE:STRING=$golangSource",
"-DGO_OUTPUT:STRING=$golangBuild",
"-DFLAVOR_NAME=$buildFlavor"
)
}
}
}
buildTypes {
named("release") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
productFlavors {
create("open") {
dimension = "open"
}
create("premium") {
dimension = "premium"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
@ -51,15 +75,60 @@ android {
}
dependencies {
implementation(project(":common"))
api(project(":common"))
implementation(libs.androidx.core)
implementation(libs.kotlin.coroutine)
implementation(libs.kotlin.serialization.json)
implementation(kotlin("stdlib-jdk7"))
implementation("androidx.core:core-ktx:$ktxVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
}
repositories {
mavenCentral()
}
task("downloadGeoipDatabase") {
val databaseFile = geoipOutput.resolve("Country.mmdb")
val moduleFile = geoipOutput.resolve("go.mod")
val sourceFile = geoipOutput.resolve("blob.go")
val moduleContent = """
module "cfa/blob"
""".trimIndent()
val sourceContent = """
package blob
import _ "embed"
//go:embed Country.mmdb
var GeoipDatabase []byte
""".trimIndent()
onlyIf {
System.currentTimeMillis() - databaseFile.lastModified() > geoipInvalidate.toMillis()
}
doLast {
geoipOutput.mkdirs()
moduleFile.writeText(moduleContent)
sourceFile.writeText(sourceContent)
URL(geoipDatabaseUrl).openConnection().getInputStream().use { input ->
FileOutputStream(databaseFile).use { output ->
input.copyTo(output)
}
}
}
}
afterEvaluate {
tasks.withType(GolangBuildTask::class.java).forEach {
it.inputs.dir(golangSource)
val downloadTask = tasks["downloadGeoipDatabase"]
tasks.forEach {
if (it.name.startsWith("externalGolangBuild")) {
it.dependsOn(downloadTask)
}
}
}

@ -1 +0,0 @@
Subproject commit a330fa1506f84841d0f80724d12fa4add5205328

View file

@ -1,118 +0,0 @@
module foss
go 1.20
require cfa v0.0.0
require (
github.com/3andne/restls-client-go v0.1.6 // indirect
github.com/RyuaNerin/go-krypto v1.2.4 // indirect
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/coreos/go-iptables v0.7.0 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect
github.com/go-chi/render v1.0.3 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/gofrs/uuid/v5 v5.3.0 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 // indirect
github.com/metacubex/chacha v0.1.0 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect
github.com/metacubex/mihomo v1.7.0 // indirect
github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4 // indirect
github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4 // indirect
github.com/metacubex/sing-shadowsocks v0.2.8 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.2 // indirect
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 // indirect
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3 // indirect
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785 // indirect
github.com/metacubex/utls v1.6.6 // indirect
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/openacid/low v0.1.21 // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/sagernet/cors v1.2.1 // indirect
github.com/sagernet/fswatch v0.1.1 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/sing v0.5.0-alpha.13 // indirect
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/samber/lo v1.47.0 // indirect
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
)
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297
replace cfa => ../../main/golang
replace github.com/metacubex/mihomo => ./clash

View file

@ -1,278 +0,0 @@
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
github.com/RyuaNerin/elliptic2 v1.0.0/go.mod h1:wWB8fWrJI/6EPJkyV/r1Rj0hxUgrusmqSj8JN6yNf/A=
github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4b4Go=
github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBEz5nGDMvswiajqh7k8ogWRlhRwKy5mY=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5 h1:GkMacU5ftc+IEg1449N3UEy2XLDz58W4fkrRu2fibb8=
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc=
github.com/metacubex/chacha v0.1.0/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4 h1:CgdUBRxmNlxEGkp35HwvgQ10jwOOUJKWdOxpi8yWi8o=
github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4/go.mod h1:Y7yRGqFE6UQL/3aKPYmiYdjfVkeujJaStP4+jiZMcN8=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 h1:YG/JkwGPbca5rUtEMHIu8ZuqzR7BSVm1iqY8hNoMeMA=
github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4 h1:HobpULaPK6OoxrHMmgcwLkwwIduXVmwdcznwUfH1GQM=
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 h1:ypfofGDZbP8p3Y4P/m74JYu7sQViesi3c8nbmT6cS0Y=
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE=
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3 h1:xg71VmzLS6ByAzi/57phwDvjE+dLLs+ozH00k4DnOns=
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3/go.mod h1:6nitcmzPDL3MXnLdhu6Hm126Zk4S1fBbX3P7jxUxSFw=
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785 h1:NNmI+ZV0DzNuqaAInRQuZFLHlWVuyHeow8jYpdKjHjo=
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
github.com/openacid/must v0.1.3/go.mod h1:luPiXCuJlEo3UUFQngVQokV0MPGryeYvtCbQPs3U1+I=
github.com/openacid/testkeys v0.1.6/go.mod h1:MfA7cACzBpbiwekivj8StqX0WIRmqlMsci1c37CA3Do=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c/go.mod h1:NV/a66PhhWYVmUMaotlXJ8fIEFB98u+c8l/CQIEFLrU=
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e h1:ur8uMsPIFG3i4Gi093BQITvwH9znsz2VUZmnmwHvpIo=
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e5fBW3bpPyo+3uLo513gIUblc03egGjMM0+5GKbzK8=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=

View file

@ -1,5 +0,0 @@
package golang
import (
_ "cfa/native/all"
)

View file

@ -1,53 +1,7 @@
cmake_minimum_required(VERSION 3.0)
# 获取git hash
message(STATUS "CMAKE_CURRENT_SOURCE_DIR= ${CMAKE_CURRENT_SOURCE_DIR}")
execute_process(
COMMAND git submodule foreach git log -1 --format=%H
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE COMMIT_HASH
)
string(REPLACE "\n" ";" COMMIT_HASH "${COMMIT_HASH}")
list(GET COMMIT_HASH 1 COMMIT_HASH)
string (REGEX REPLACE "[\n\t\r]" "" COMMIT_HASH ${COMMIT_HASH})
string(SUBSTRING ${COMMIT_HASH} 0 7 COMMIT_HASH)
message(STATUS "git hash= ${COMMIT_HASH}")
# 获取分支名称
execute_process(
COMMAND git submodule foreach git branch -r --contains ${COMMIT_HASH}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE CURRENT_BRANCH
)
string(REPLACE "\n" ";" CURRENT_BRANCH "${CURRENT_BRANCH}")
list(GET CURRENT_BRANCH 1 CURRENT_BRANCH)
string (REGEX REPLACE "origin/" "" CURRENT_BRANCH ${CURRENT_BRANCH})
string (REGEX REPLACE "[\n\t\r]" "" CURRENT_BRANCH ${CURRENT_BRANCH})
#string(SUBSTRING ${CURRENT_BRANCH} 0 8 CURRENT_BRANCH)
message(STATUS "git current branch = ${CURRENT_BRANCH}")
# 获取生成时间
string(TIMESTAMP COMPILE_TIME "%y%m%d")
string (REGEX REPLACE "[\n\t\r]" "" COMPILE_TIME ${COMPILE_TIME})
string(REGEX REPLACE "\"" "" COMPILE_TIME ${COMPILE_TIME})
# 生成版本信息
set(GIT_VERSION "${CURRENT_BRANCH}_${COMMIT_HASH}_${COMPILE_TIME}")
message(STATUS "version info = ${GIT_VERSION}")
# 去除空格
string(REGEX REPLACE "[ ]+" "" GIT_VERSION "${GIT_VERSION}")
# 保存变量到文件
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/version.h @ONLY)
project(clash-bridge C)
set(CMAKE_POSITION_INDEPENDENT_CODE on)
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
set(GO_OUTPUT_BASE ${GO_OUTPUT}/${FLAVOR_NAME})
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
@ -66,4 +20,4 @@ include_directories("${GO_SOURCE}")
link_directories("${GO_OUTPUT_BASE}/${CMAKE_ANDROID_ARCH_ABI}")
add_library(bridge SHARED main.c jni_helper.c bridge_helper.c)
target_link_libraries(bridge log clash)
target_link_libraries(bridge log clash)

View file

@ -54,27 +54,20 @@ int jni_catch_exception(JNIEnv *env) {
return result;
}
void jni_attach_thread(struct _scoped_jni *jni) {
void jni_attach_thread(JNIEnv **penv) {
JavaVM *vm = global_java_vm();
if ((*vm)->GetEnv(vm, (void **) &jni->env, JNI_VERSION_1_6) == JNI_OK) {
jni->require_release = 0;
return;
}
if ((*vm)->AttachCurrentThread(vm, &jni->env, NULL) != JNI_OK) {
if ((*vm)->AttachCurrentThread(vm, penv, NULL) != JNI_OK) {
abort();
}
jni->require_release = 1;
}
void jni_detach_thread(struct _scoped_jni *jni) {
void jni_detach_thread(JNIEnv **env) {
(void) env;
JavaVM *vm = global_java_vm();
if (jni->require_release) {
(*vm)->DetachCurrentThread(vm);
}
(*vm)->DetachCurrentThread(vm);
}
void release_string(char **str) {

View file

@ -6,23 +6,21 @@
#include <malloc.h>
#include <android/log.h>
struct _scoped_jni {
JNIEnv *env;
int require_release;
};
extern void initialize_jni(JavaVM *vm, JNIEnv *env);
extern jstring jni_new_string(JNIEnv *env, const char *str);
extern char *jni_get_string(JNIEnv *env, jstring str);
extern int jni_catch_exception(JNIEnv *env);
extern void jni_attach_thread(struct _scoped_jni *jni);
extern void jni_detach_thread(struct _scoped_jni *env);
extern void jni_attach_thread(JNIEnv **penv);
extern void jni_detach_thread(JNIEnv **env);
extern void release_string(char **str);
#define ATTACH_JNI() __attribute__((unused, cleanup(jni_detach_thread))) \
struct _scoped_jni _jni; \
jni_attach_thread(&_jni); \
JNIEnv *env = _jni.env
#define ATTACH_JNI() __attribute__((unused, cleanup(jni_detach_thread))) JNIEnv *env = NULL; jni_attach_thread(&env)
#define scoped_string __attribute__((cleanup(release_string))) char*

View file

@ -8,9 +8,6 @@
#include "jni_helper.h"
#include "trace.h"
#include "version.h" // 添加当前编译core版本号变量
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeInit(JNIEnv *env, jobject thiz,
jstring home,
@ -87,16 +84,6 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeNotifyDnsChanged(JNIEnv *en
notifyDnsChanged(_dns_list);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeNotifyTimeZoneChanged(JNIEnv *env, jobject thiz,
jstring name, jint offset) {
TRACE_METHOD();
scoped_string _name = get_string(name);
notifyTimeZoneChanged(_name, offset);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeNotifyInstalledAppChanged(JNIEnv *env,
jobject thiz,
@ -109,22 +96,18 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeNotifyInstalledAppChanged(J
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartTun(JNIEnv *env, jobject thiz,
jint fd,
jstring stack,
jstring gateway,
jstring portal,
jstring dns,
Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartTun(JNIEnv *env, jobject thiz, jint fd,
jint mtu, jstring gateway,
jstring mirror, jstring dns,
jobject cb) {
TRACE_METHOD();
scoped_string _stack = get_string(stack);
scoped_string _gateway = get_string(gateway);
scoped_string _portal = get_string(portal);
scoped_string _mirror = get_string(mirror);
scoped_string _dns = get_string(dns);
jobject _interface = new_global(cb);
startTun(fd, _stack, _gateway, _portal, _dns, _interface);
startTun(fd, mtu, _gateway, _mirror, _dns, _interface);
}
JNIEXPORT void JNICALL
@ -289,6 +272,33 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeClearOverride(JNIEnv *env,
clearOverride(slot);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeInstallSideloadGeoip(JNIEnv *env, jobject thiz,
jbyteArray data) {
TRACE_METHOD();
if (data == NULL) {
installSideloadGeoip(NULL, 0);
return;
}
jbyte *bytes = (*env)->GetByteArrayElements(env, data, NULL);
int size = (*env)->GetArrayLength(env, data);
scoped_string err = installSideloadGeoip(bytes, size);
(*env)->ReleaseByteArrayElements(env, data, bytes, JNI_ABORT);
if (err != NULL) {
(*env)->ThrowNew(
env,
find_class("com/github/kr328/clash/core/bridge/ClashException"),
err
);
}
}
JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeQueryConfiguration(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
@ -308,7 +318,6 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeSubscribeLogcat(JNIEnv *env
subscribeLogcat(_callback);
}
static jmethodID m_tun_interface_mark_socket;
static jmethodID m_tun_interface_query_socket_uid;
static jmethodID m_completable_complete;
@ -516,13 +525,4 @@ JNI_OnLoad(JavaVM *vm, void *reserved) {
release_object_func = &release_jni_object_impl;
return JNI_VERSION_1_6;
}
JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeCoreVersion(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
char* Version = make_String(GIT_VERSION);
return new_string(Version);
}

View file

@ -1,12 +0,0 @@
#ifndef VERSION_H_IN
#define VERSION_H_IN
/**
* core版本号
*/
#define GIT_VERSION @GIT_VERSION@
#define make_Str(x) #x
#define make_String(x) make_Str(x)
#endif

View file

@ -5,6 +5,7 @@
<option name="MOVE_ALL_IMPORTS_IN_ONE_DECLARATION" value="true" />
<option name="MOVE_ALL_STDLIB_IMPORTS_IN_ONE_GROUP" value="true" />
<option name="GROUP_STDLIB_IMPORTS" value="true" />
<option name="GROUP_CURRENT_PROJECT_IMPORTS" value="true" />
</GoCodeStyleSettings>
</code_scheme>
</component>

View file

@ -7,9 +7,9 @@ import (
"errors"
"unsafe"
"cfa/native/app"
"cfa/app"
"github.com/metacubex/mihomo/log"
"github.com/Dreamacro/clash/log"
)
func openRemoteContent(url string) (int, error) {
@ -43,12 +43,6 @@ func notifyInstalledAppsChanged(uids C.c_string) {
app.NotifyInstallAppsChanged(u)
}
//export notifyTimeZoneChanged
func notifyTimeZoneChanged(name C.c_string, offset C.int) {
app.NotifyTimeZoneChanged(C.GoString(name), int(offset))
}
//export queryConfiguration
func queryConfiguration() *C.char {
response := &struct{}{}

View file

@ -3,7 +3,6 @@ package app
import (
"strconv"
"strings"
"time"
)
var appVersionName string
@ -47,7 +46,3 @@ func NotifyInstallAppsChanged(uidList string) {
func QueryAppByUid(uid int) string {
return installedAppsUid[uid]
}
func NotifyTimeZoneChanged(name string, offset int) {
time.Local = time.FixedZone(name, offset)
}

View file

@ -0,0 +1,15 @@
package app
import "strings"
var systemDns []string
func NotifyDnsChanged(dnsList string) {
dns := strings.Split(dnsList, ",")
systemDns = dns
}
func SystemDns() []string {
return systemDns
}

View file

@ -1,10 +1,10 @@
package app
import (
"cfa/platform"
"net"
"strings"
"syscall"
"cfa/native/platform"
)
var markSocketImpl func(fd int)
@ -15,15 +15,10 @@ func MarkSocket(fd int) {
}
func QuerySocketUid(source, target net.Addr) int {
var protocol int
protocol := syscall.IPPROTO_TCP
switch source.Network() {
case "udp", "udp4", "udp6":
if strings.HasPrefix(source.String(), "udp") {
protocol = syscall.IPPROTO_UDP
case "tcp", "tcp4", "tcp6":
protocol = syscall.IPPROTO_TCP
default:
return -1
}
if PlatformVersion() < 29 {

Some files were not shown because too many files have changed in this diff Show more