mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-22 16:07:49 +08:00
Update On Mon Feb 9 20:27:08 CET 2026
This commit is contained in:
@@ -1266,3 +1266,4 @@ Update On Thu Feb 5 20:02:24 CET 2026
|
||||
Update On Fri Feb 6 20:02:22 CET 2026
|
||||
Update On Sat Feb 7 19:47:34 CET 2026
|
||||
Update On Sun Feb 8 19:51:33 CET 2026
|
||||
Update On Mon Feb 9 20:27:00 CET 2026
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.getSystemService
|
||||
import com.github.kr328.clash.common.compat.isAllowForceDarkCompat
|
||||
import com.github.kr328.clash.common.compat.isLightNavigationBarCompat
|
||||
import com.github.kr328.clash.common.compat.isLightStatusBarsCompat
|
||||
@@ -88,6 +90,11 @@ abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
|
||||
super.onCreate(savedInstanceState)
|
||||
applyDayNight()
|
||||
|
||||
// Apply excludeFromRecents setting to all app tasks.
|
||||
checkNotNull(getSystemService<ActivityManager>()).appTasks.forEach { task ->
|
||||
task.setExcludeFromRecents(uiStore.hideFromRecents)
|
||||
}
|
||||
|
||||
launch {
|
||||
main()
|
||||
}
|
||||
|
||||
+11
@@ -77,6 +77,17 @@ class AppSettingsDesign(
|
||||
}
|
||||
}
|
||||
|
||||
switch(
|
||||
value = uiStore::hideFromRecents,
|
||||
icon = R.drawable.ic_baseline_stack,
|
||||
title = R.string.hide_from_recents_title,
|
||||
summary = R.string.hide_from_recents_desc,
|
||||
) {
|
||||
listener = OnChangedListener {
|
||||
requests.trySend(Request.ReCreateAllActivities)
|
||||
}
|
||||
}
|
||||
|
||||
category(R.string.service)
|
||||
|
||||
switch(
|
||||
|
||||
@@ -30,6 +30,11 @@ class UiStore(context: Context) {
|
||||
defaultValue = false
|
||||
)
|
||||
|
||||
var hideFromRecents: Boolean by store.boolean(
|
||||
key = "hide_from_recents",
|
||||
defaultValue = false,
|
||||
)
|
||||
|
||||
var proxyExcludeNotSelectable by store.boolean(
|
||||
key = "proxy_exclude_not_selectable",
|
||||
defaultValue = false,
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M1024,320 L512,64 0,320l512,256L1024,320zM512,149 L854,320 512,491 170,320 512,149zM921.4,460.7 L1024,512 512,768 0,512 102.6,460.7 512,665.4ZM921.4,652.7 L1024,704 512,960 0,704 102.6,652.7 512,857.4Z" />
|
||||
</vector>
|
||||
@@ -39,6 +39,8 @@
|
||||
<string name="global_mode">全局模式</string>
|
||||
<string name="hide_app_icon_title">隐藏应用图标</string>
|
||||
<string name="hide_app_icon_desc">可以在拨号盘输入 *#*#252746382#*#* 打开应用</string>
|
||||
<string name="hide_from_recents_title">从最近任务隐藏</string>
|
||||
<string name="hide_from_recents_desc">在最近任务中隐藏应用</string>
|
||||
<string name="history">历史</string>
|
||||
<string name="import_from_file">从文件导入</string>
|
||||
<string name="import_from_url">从 URL 导入</string>
|
||||
|
||||
@@ -346,6 +346,8 @@
|
||||
|
||||
<string name="hide_app_icon_title">Hide App Icon</string>
|
||||
<string name="hide_app_icon_desc">You can dial *#*#252746382#*#* to open this App</string>
|
||||
<string name="hide_from_recents_title">Hide from Recents</string>
|
||||
<string name="hide_from_recents_desc">Hide app from the Recent apps screen</string>
|
||||
<string name="import_from_qr_no_permission">Camera access is restricted. Please enable it in Settings.</string>
|
||||
<string name="import_from_qr_exception">An unhandled system exception occurred.</string>
|
||||
</resources>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx4g -XX:+UseZGC -Dfile.encoding=UTF-8
|
||||
org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
|
||||
@@ -60,7 +60,7 @@ func (o *overrideSchema) Apply(mapping map[string]any) error {
|
||||
mapping["skip-cert-verify"] = *o.SkipCertVerify
|
||||
}
|
||||
if o.Interface != nil {
|
||||
mapping["interface"] = *o.Interface
|
||||
mapping["interface-name"] = *o.Interface
|
||||
}
|
||||
if o.RoutingMark != nil {
|
||||
mapping["routing-mark"] = *o.RoutingMark
|
||||
|
||||
+3
-3
@@ -55,7 +55,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
|
||||
- uses: actions/cache@v5
|
||||
name: Cache Rust dependencies
|
||||
@@ -142,7 +142,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
|
||||
- uses: actions/cache@v5
|
||||
name: Cache Rust dependencies
|
||||
@@ -233,7 +233,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
|
||||
- uses: actions/cache@v5
|
||||
name: Cache Rust dependencies
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version: '24'
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version: '24'
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ jobs:
|
||||
- name: Install Node latest
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ jobs:
|
||||
- name: Install Node latest
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
- uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v2.x
|
||||
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
- name: Install Node latest
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
- name: Install Node latest
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
@@ -19,6 +19,8 @@ jobs:
|
||||
update_tag:
|
||||
name: Update tag
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version: '24'
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Prepare Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
GITHUB_REPO: ${{ github.repository }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Commit changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v6
|
||||
uses: stefanzweifel/git-auto-commit-action@v7
|
||||
with:
|
||||
commit_message: 'chore: bump version to v${{ steps.update-version.outputs.version }}'
|
||||
commit_user_name: 'github-actions[bot]'
|
||||
|
||||
@@ -76,9 +76,9 @@
|
||||
"@iconify/json": "2.2.437",
|
||||
"@monaco-editor/react": "4.7.0",
|
||||
"@tanstack/react-query": "5.90.20",
|
||||
"@tanstack/react-router": "1.158.4",
|
||||
"@tanstack/react-router-devtools": "1.158.4",
|
||||
"@tanstack/router-plugin": "1.158.4",
|
||||
"@tanstack/react-router": "1.159.4",
|
||||
"@tanstack/react-router-devtools": "1.159.4",
|
||||
"@tanstack/router-plugin": "1.159.4",
|
||||
"@tauri-apps/plugin-clipboard-manager": "2.3.2",
|
||||
"@tauri-apps/plugin-dialog": "2.6.0",
|
||||
"@tauri-apps/plugin-fs": "2.4.5",
|
||||
@@ -102,14 +102,14 @@
|
||||
"nanoid": "5.1.6",
|
||||
"sass-embedded": "1.97.3",
|
||||
"shiki": "2.5.0",
|
||||
"unplugin-auto-import": "20.3.0",
|
||||
"unplugin-icons": "22.5.0",
|
||||
"unplugin-auto-import": "21.0.0",
|
||||
"unplugin-icons": "23.0.1",
|
||||
"validator": "13.15.26",
|
||||
"vite": "7.3.1",
|
||||
"vite-plugin-html": "3.2.2",
|
||||
"vite-plugin-sass-dts": "1.3.35",
|
||||
"vite-plugin-svgr": "4.5.0",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vite-tsconfig-paths": "6.1.0",
|
||||
"zod": "4.3.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"react-use": "17.6.0",
|
||||
"tailwindcss": "4.1.18",
|
||||
"vite": "7.3.1",
|
||||
"vite-tsconfig-paths": "5.1.4"
|
||||
"vite-tsconfig-paths": "6.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@emotion/react": "11.14.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"latest": {
|
||||
"mihomo": "v1.19.19",
|
||||
"mihomo": "v1.19.20",
|
||||
"mihomo_alpha": "alpha-97f2525",
|
||||
"clash_rs": "v0.9.4",
|
||||
"clash_premium": "2023-09-05-gdcc8d87",
|
||||
@@ -69,5 +69,5 @@
|
||||
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
|
||||
}
|
||||
},
|
||||
"updated_at": "2026-02-07T22:32:42.838Z"
|
||||
"updated_at": "2026-02-08T22:48:51.230Z"
|
||||
}
|
||||
|
||||
@@ -93,9 +93,9 @@
|
||||
"tsx": "4.21.0",
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
"packageManager": "pnpm@10.29.1",
|
||||
"packageManager": "pnpm@10.29.2",
|
||||
"engines": {
|
||||
"node": "22.22.0"
|
||||
"node": "24.13.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
|
||||
Generated
+83
-124
@@ -241,7 +241,7 @@ importers:
|
||||
version: 3.13.18(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@tanstack/router-zod-adapter':
|
||||
specifier: 1.81.5
|
||||
version: 1.81.5(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6)
|
||||
version: 1.81.5(@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6)
|
||||
'@tauri-apps/api':
|
||||
specifier: 2.10.1
|
||||
version: 2.10.1
|
||||
@@ -355,14 +355,14 @@ importers:
|
||||
specifier: 5.90.20
|
||||
version: 5.90.20(react@19.2.4)
|
||||
'@tanstack/react-router':
|
||||
specifier: 1.158.4
|
||||
version: 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
specifier: 1.159.4
|
||||
version: 1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@tanstack/react-router-devtools':
|
||||
specifier: 1.158.4
|
||||
version: 1.158.4(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.158.4)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
specifier: 1.159.4
|
||||
version: 1.159.4(@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.159.4)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@tanstack/router-plugin':
|
||||
specifier: 1.158.4
|
||||
version: 1.158.4(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
|
||||
specifier: 1.159.4
|
||||
version: 1.159.4(@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
|
||||
'@tauri-apps/plugin-clipboard-manager':
|
||||
specifier: 2.3.2
|
||||
version: 2.3.2
|
||||
@@ -433,11 +433,11 @@ importers:
|
||||
specifier: 2.5.0
|
||||
version: 2.5.0
|
||||
unplugin-auto-import:
|
||||
specifier: 20.3.0
|
||||
version: 20.3.0
|
||||
specifier: 21.0.0
|
||||
version: 21.0.0
|
||||
unplugin-icons:
|
||||
specifier: 22.5.0
|
||||
version: 22.5.0(@svgr/core@8.1.0(typescript@5.9.3))
|
||||
specifier: 23.0.1
|
||||
version: 23.0.1(@svgr/core@8.1.0(typescript@5.9.3))
|
||||
validator:
|
||||
specifier: 13.15.26
|
||||
version: 13.15.26
|
||||
@@ -454,8 +454,8 @@ importers:
|
||||
specifier: 4.5.0
|
||||
version: 4.5.0(rollup@4.46.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
|
||||
vite-tsconfig-paths:
|
||||
specifier: 5.1.4
|
||||
version: 5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
|
||||
specifier: 6.1.0
|
||||
version: 6.1.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
|
||||
zod:
|
||||
specifier: 4.3.6
|
||||
version: 4.3.6
|
||||
@@ -523,8 +523,8 @@ importers:
|
||||
specifier: 7.3.1
|
||||
version: 7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1)
|
||||
vite-tsconfig-paths:
|
||||
specifier: 5.1.4
|
||||
version: 5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
|
||||
specifier: 6.1.0
|
||||
version: 6.1.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
|
||||
devDependencies:
|
||||
'@emotion/react':
|
||||
specifier: 11.14.0
|
||||
@@ -640,9 +640,6 @@ packages:
|
||||
'@antfu/install-pkg@1.1.0':
|
||||
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
|
||||
|
||||
'@antfu/utils@9.2.0':
|
||||
resolution: {integrity: sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw==}
|
||||
|
||||
'@babel/code-frame@7.27.1':
|
||||
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -1678,8 +1675,8 @@ packages:
|
||||
'@iconify/types@2.0.0':
|
||||
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
||||
|
||||
'@iconify/utils@3.0.2':
|
||||
resolution: {integrity: sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==}
|
||||
'@iconify/utils@3.1.0':
|
||||
resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==}
|
||||
|
||||
'@inlang/paraglide-js@2.7.1':
|
||||
resolution: {integrity: sha512-wCpnS9iRTRYMilvWBjB0ndf8+moon+AXz23Uh4wbpQjWhRJyvCytkGFzm7jeqAGggK4v3oeuyjva91TDMS+qhw==}
|
||||
@@ -3372,20 +3369,20 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^18 || ^19
|
||||
|
||||
'@tanstack/react-router-devtools@1.158.4':
|
||||
resolution: {integrity: sha512-/EkrrJGTPC7MwLfcYYmZM71ANDMLbwcYvBtDA+48LqHUKal8mpWlaodiWdFFnVQ7ny/unbUxljgdrNV9YZiyFQ==}
|
||||
'@tanstack/react-router-devtools@1.159.4':
|
||||
resolution: {integrity: sha512-7HXV4b5WZMdWoP6HD+mURh4mq1ssRg0dfcVYx+AzhaLboFzy4LyzdUtMpmNgRFgz3mBXLBoo+gMbKSjKlmsZmw==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
'@tanstack/react-router': ^1.158.4
|
||||
'@tanstack/router-core': ^1.158.4
|
||||
'@tanstack/react-router': ^1.159.4
|
||||
'@tanstack/router-core': ^1.159.4
|
||||
react: '>=18.0.0 || >=19.0.0'
|
||||
react-dom: '>=18.0.0 || >=19.0.0'
|
||||
peerDependenciesMeta:
|
||||
'@tanstack/router-core':
|
||||
optional: true
|
||||
|
||||
'@tanstack/react-router@1.158.4':
|
||||
resolution: {integrity: sha512-i15xXumgvpuM+4NSuIwgouGezuj9eHjZsgpTZSQ7E9pa8rYmhZbWnf8xU68qaLmaKIol/e75o/YzVH2QWHs3iQ==}
|
||||
'@tanstack/react-router@1.159.4':
|
||||
resolution: {integrity: sha512-z3DhNkRh/joky5b+X4jEYOn9q4Jieie6mVFP62wgwM9pVlNRYh6aIroiU95ZyOwDXDijItVEZtvHuipbLHy4jw==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
react: '>=18.0.0 || >=19.0.0'
|
||||
@@ -3416,30 +3413,30 @@ packages:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@tanstack/router-core@1.158.4':
|
||||
resolution: {integrity: sha512-KikgYdyrEFqsjjgv9pMhDTMmASMAyFRvUiKFdQPQtXq3aD1qv/zck4CbA4bfzp9N9nYu/qvWwU1mlYU4u5JeXg==}
|
||||
'@tanstack/router-core@1.159.4':
|
||||
resolution: {integrity: sha512-MFzPH39ijNO83qJN3pe7x4iAlhZyqgao3sJIzv3SJ4Pnk12xMnzuDzIAQT/1WV6JolPQEcw0Wr4L5agF8yxoeg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@tanstack/router-devtools-core@1.158.4':
|
||||
resolution: {integrity: sha512-9MKzstYp/6sNRSwJY2b9ipVW8b8/x1iSFNfLhOJur2tnjB3RhwCDfy0u+to70BrRpBEWeq7jvJoVdP029gzUUg==}
|
||||
'@tanstack/router-devtools-core@1.159.4':
|
||||
resolution: {integrity: sha512-qMUeIv+6n1mZOcO2raCIbdOeDeMpJEmgm6oMs/nWEG61lYrzJYaCcpBTviAX0nRhSiQSUCX9cHiosUEA0e2HAw==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
'@tanstack/router-core': ^1.158.4
|
||||
'@tanstack/router-core': ^1.159.4
|
||||
csstype: ^3.0.10
|
||||
peerDependenciesMeta:
|
||||
csstype:
|
||||
optional: true
|
||||
|
||||
'@tanstack/router-generator@1.158.4':
|
||||
resolution: {integrity: sha512-RQmqMTT0oV8dS/3Glcq9SPzDZqOPyKb/LVFUkNoTfMwW88WyGnQcYqZAkmVk/CGBWWDfwObOUZoGq5jTF7bG8w==}
|
||||
'@tanstack/router-generator@1.159.4':
|
||||
resolution: {integrity: sha512-O8tICQoSuvK6vs3mvBdI3zVLFmYfj/AYDCX0a5msSADP/2S0GsgDDTB5ah731TqYCtjeNriaWz9iqst38cjF/Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@tanstack/router-plugin@1.158.4':
|
||||
resolution: {integrity: sha512-g2sytAhljw6Jd6Klu37OZ75+o+vhiGdbWtnBy/4rYLC4NN6hSnjgJQRI3+h1CI1KQ4EUgsZYZr/hgE1KHoiWYQ==}
|
||||
'@tanstack/router-plugin@1.159.4':
|
||||
resolution: {integrity: sha512-xXLUPwIf1Y+VGrpryHZYoJoG7V5evxTkmP64CYm6JEJGTb3hai/syhZb69iVQYb4f4IR5LqEL7VgagnlekdAWw==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
'@rsbuild/core': '>=1.0.2'
|
||||
'@tanstack/react-router': ^1.158.4
|
||||
'@tanstack/react-router': ^1.159.4
|
||||
vite: '>=5.0.0 || >=6.0.0 || >=7.0.0'
|
||||
vite-plugin-solid: ^2.11.10
|
||||
webpack: '>=5.92.0'
|
||||
@@ -4681,15 +4678,6 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.3.7:
|
||||
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.4.0:
|
||||
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -5087,10 +5075,6 @@ packages:
|
||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
globals@15.15.0:
|
||||
resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
globals@17.3.0:
|
||||
resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5959,6 +5943,9 @@ packages:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
obug@2.1.1:
|
||||
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
|
||||
|
||||
octokit@5.0.5:
|
||||
resolution: {integrity: sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==}
|
||||
engines: {node: '>= 20'}
|
||||
@@ -6827,6 +6814,11 @@ packages:
|
||||
peerDependencies:
|
||||
kysely: '*'
|
||||
|
||||
srvx@0.11.2:
|
||||
resolution: {integrity: sha512-u6NbjE84IJwm1XUnJ53WqylLTQ3BdWRw03lcjBNNeMBD+EFjkl0Cnw1RVaGSqRAo38pOHOPXJH30M6cuTINUxw==}
|
||||
engines: {node: '>=20.16.0'}
|
||||
hasBin: true
|
||||
|
||||
stack-generator@2.0.10:
|
||||
resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==}
|
||||
|
||||
@@ -7152,8 +7144,8 @@ packages:
|
||||
unified@11.0.4:
|
||||
resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==}
|
||||
|
||||
unimport@5.5.0:
|
||||
resolution: {integrity: sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==}
|
||||
unimport@5.6.0:
|
||||
resolution: {integrity: sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==}
|
||||
engines: {node: '>=18.12.0'}
|
||||
|
||||
unist-util-is@6.0.0:
|
||||
@@ -7187,9 +7179,9 @@ packages:
|
||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
unplugin-auto-import@20.3.0:
|
||||
resolution: {integrity: sha512-RcSEQiVv7g0mLMMXibYVKk8mpteKxvyffGuDKqZZiFr7Oq3PB1HwgHdK5O7H4AzbhzHoVKG0NnMnsk/1HIVYzQ==}
|
||||
engines: {node: '>=14'}
|
||||
unplugin-auto-import@21.0.0:
|
||||
resolution: {integrity: sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
peerDependencies:
|
||||
'@nuxt/kit': ^4.0.0
|
||||
'@vueuse/core': '*'
|
||||
@@ -7199,15 +7191,13 @@ packages:
|
||||
'@vueuse/core':
|
||||
optional: true
|
||||
|
||||
unplugin-icons@22.5.0:
|
||||
resolution: {integrity: sha512-MBlMtT5RuMYZy4TZgqUL2OTtOdTUVsS1Mhj6G1pEzMlFJlEnq6mhUfoIt45gBWxHcsOdXJDWLg3pRZ+YmvAVWQ==}
|
||||
unplugin-icons@23.0.1:
|
||||
resolution: {integrity: sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ==}
|
||||
peerDependencies:
|
||||
'@svgr/core': '>=7.0.0'
|
||||
'@svgx/core': ^1.0.1
|
||||
'@vue/compiler-sfc': ^3.0.2 || ^2.7.0
|
||||
'@vue/compiler-sfc': ^3.0.2
|
||||
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0
|
||||
vue-template-compiler: ^2.6.12
|
||||
vue-template-es2015-compiler: ^1.9.0
|
||||
peerDependenciesMeta:
|
||||
'@svgr/core':
|
||||
optional: true
|
||||
@@ -7217,19 +7207,11 @@ packages:
|
||||
optional: true
|
||||
svelte:
|
||||
optional: true
|
||||
vue-template-compiler:
|
||||
optional: true
|
||||
vue-template-es2015-compiler:
|
||||
optional: true
|
||||
|
||||
unplugin-utils@0.3.1:
|
||||
resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
unplugin@2.3.10:
|
||||
resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==}
|
||||
engines: {node: '>=18.12.0'}
|
||||
|
||||
unplugin@2.3.11:
|
||||
resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==}
|
||||
engines: {node: '>=18.12.0'}
|
||||
@@ -7360,13 +7342,10 @@ packages:
|
||||
peerDependencies:
|
||||
vite: '>=2.6.0'
|
||||
|
||||
vite-tsconfig-paths@5.1.4:
|
||||
resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==}
|
||||
vite-tsconfig-paths@6.1.0:
|
||||
resolution: {integrity: sha512-kpd3sY9glHIDaq4V/Tlc1Y8WaKtutoc3B525GHxEVKWX42FKfQsXvjFOemu1I8VIN8pNbrMLWVTbW79JaRUxKg==}
|
||||
peerDependencies:
|
||||
vite: '*'
|
||||
peerDependenciesMeta:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
vite@7.3.1:
|
||||
resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==}
|
||||
@@ -7564,8 +7543,6 @@ snapshots:
|
||||
package-manager-detector: 1.3.0
|
||||
tinyexec: 1.0.1
|
||||
|
||||
'@antfu/utils@9.2.0': {}
|
||||
|
||||
'@babel/code-frame@7.27.1':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
@@ -8812,18 +8789,11 @@ snapshots:
|
||||
|
||||
'@iconify/types@2.0.0': {}
|
||||
|
||||
'@iconify/utils@3.0.2':
|
||||
'@iconify/utils@3.1.0':
|
||||
dependencies:
|
||||
'@antfu/install-pkg': 1.1.0
|
||||
'@antfu/utils': 9.2.0
|
||||
'@iconify/types': 2.0.0
|
||||
debug: 4.4.3
|
||||
globals: 15.15.0
|
||||
kolorist: 1.8.0
|
||||
local-pkg: 1.1.2
|
||||
mlly: 1.8.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@inlang/paraglide-js@2.7.1(babel-plugin-macros@3.1.0)':
|
||||
dependencies:
|
||||
@@ -10384,25 +10354,26 @@ snapshots:
|
||||
'@tanstack/query-core': 5.90.20
|
||||
react: 19.2.4
|
||||
|
||||
'@tanstack/react-router-devtools@1.158.4(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.158.4)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@tanstack/react-router-devtools@1.159.4(@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.159.4)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@tanstack/react-router': 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@tanstack/router-devtools-core': 1.158.4(@tanstack/router-core@1.158.4)(csstype@3.2.3)
|
||||
'@tanstack/react-router': 1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@tanstack/router-devtools-core': 1.159.4(@tanstack/router-core@1.159.4)(csstype@3.2.3)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@tanstack/router-core': 1.158.4
|
||||
'@tanstack/router-core': 1.159.4
|
||||
transitivePeerDependencies:
|
||||
- csstype
|
||||
|
||||
'@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@tanstack/history': 1.154.14
|
||||
'@tanstack/react-store': 0.8.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@tanstack/router-core': 1.158.4
|
||||
'@tanstack/router-core': 1.159.4
|
||||
isbot: 5.1.28
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
srvx: 0.11.2
|
||||
tiny-invariant: 1.3.3
|
||||
tiny-warning: 1.0.3
|
||||
|
||||
@@ -10431,7 +10402,7 @@ snapshots:
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@tanstack/router-core@1.158.4':
|
||||
'@tanstack/router-core@1.159.4':
|
||||
dependencies:
|
||||
'@tanstack/history': 1.154.14
|
||||
'@tanstack/store': 0.8.0
|
||||
@@ -10441,18 +10412,18 @@ snapshots:
|
||||
tiny-invariant: 1.3.3
|
||||
tiny-warning: 1.0.3
|
||||
|
||||
'@tanstack/router-devtools-core@1.158.4(@tanstack/router-core@1.158.4)(csstype@3.2.3)':
|
||||
'@tanstack/router-devtools-core@1.159.4(@tanstack/router-core@1.159.4)(csstype@3.2.3)':
|
||||
dependencies:
|
||||
'@tanstack/router-core': 1.158.4
|
||||
'@tanstack/router-core': 1.159.4
|
||||
clsx: 2.1.1
|
||||
goober: 2.1.16(csstype@3.2.3)
|
||||
tiny-invariant: 1.3.3
|
||||
optionalDependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@tanstack/router-generator@1.158.4':
|
||||
'@tanstack/router-generator@1.159.4':
|
||||
dependencies:
|
||||
'@tanstack/router-core': 1.158.4
|
||||
'@tanstack/router-core': 1.159.4
|
||||
'@tanstack/router-utils': 1.158.0
|
||||
'@tanstack/virtual-file-routes': 1.154.7
|
||||
prettier: 3.8.1
|
||||
@@ -10463,7 +10434,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@tanstack/router-plugin@1.158.4(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))':
|
||||
'@tanstack/router-plugin@1.159.4(@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.0)
|
||||
@@ -10471,15 +10442,15 @@ snapshots:
|
||||
'@babel/template': 7.28.6
|
||||
'@babel/traverse': 7.29.0
|
||||
'@babel/types': 7.29.0
|
||||
'@tanstack/router-core': 1.158.4
|
||||
'@tanstack/router-generator': 1.158.4
|
||||
'@tanstack/router-core': 1.159.4
|
||||
'@tanstack/router-generator': 1.159.4
|
||||
'@tanstack/router-utils': 1.158.0
|
||||
'@tanstack/virtual-file-routes': 1.154.7
|
||||
chokidar: 3.6.0
|
||||
unplugin: 2.3.11
|
||||
zod: 3.25.76
|
||||
optionalDependencies:
|
||||
'@tanstack/react-router': 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@tanstack/react-router': 1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
vite: 7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -10498,9 +10469,9 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6)':
|
||||
'@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@tanstack/react-router': 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@tanstack/react-router': 1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
zod: 4.3.6
|
||||
|
||||
'@tanstack/store@0.8.0': {}
|
||||
@@ -11806,10 +11777,6 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.0.0
|
||||
|
||||
debug@4.3.7:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
debug@4.4.0:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -12195,8 +12162,6 @@ snapshots:
|
||||
globals@11.12.0:
|
||||
optional: true
|
||||
|
||||
globals@15.15.0: {}
|
||||
|
||||
globals@17.3.0: {}
|
||||
|
||||
globby@16.1.0:
|
||||
@@ -13135,6 +13100,8 @@ snapshots:
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
obug@2.1.1: {}
|
||||
|
||||
octokit@5.0.5:
|
||||
dependencies:
|
||||
'@octokit/app': 16.1.2
|
||||
@@ -13969,6 +13936,8 @@ snapshots:
|
||||
'@sqlite.org/sqlite-wasm': 3.48.0-build4
|
||||
kysely: 0.27.6
|
||||
|
||||
srvx@0.11.2: {}
|
||||
|
||||
stack-generator@2.0.10:
|
||||
dependencies:
|
||||
stackframe: 1.3.4
|
||||
@@ -14346,7 +14315,7 @@ snapshots:
|
||||
trough: 2.2.0
|
||||
vfile: 6.0.1
|
||||
|
||||
unimport@5.5.0:
|
||||
unimport@5.6.0:
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
escape-string-regexp: 5.0.0
|
||||
@@ -14399,39 +14368,30 @@ snapshots:
|
||||
|
||||
universalify@2.0.1: {}
|
||||
|
||||
unplugin-auto-import@20.3.0:
|
||||
unplugin-auto-import@21.0.0:
|
||||
dependencies:
|
||||
local-pkg: 1.1.2
|
||||
magic-string: 0.30.21
|
||||
picomatch: 4.0.3
|
||||
unimport: 5.5.0
|
||||
unimport: 5.6.0
|
||||
unplugin: 2.3.11
|
||||
unplugin-utils: 0.3.1
|
||||
|
||||
unplugin-icons@22.5.0(@svgr/core@8.1.0(typescript@5.9.3)):
|
||||
unplugin-icons@23.0.1(@svgr/core@8.1.0(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@antfu/install-pkg': 1.1.0
|
||||
'@iconify/utils': 3.0.2
|
||||
debug: 4.4.3
|
||||
'@iconify/utils': 3.1.0
|
||||
local-pkg: 1.1.2
|
||||
unplugin: 2.3.10
|
||||
obug: 2.1.1
|
||||
unplugin: 2.3.11
|
||||
optionalDependencies:
|
||||
'@svgr/core': 8.1.0(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
unplugin-utils@0.3.1:
|
||||
dependencies:
|
||||
pathe: 2.0.3
|
||||
picomatch: 4.0.3
|
||||
|
||||
unplugin@2.3.10:
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
acorn: 8.15.0
|
||||
picomatch: 4.0.3
|
||||
webpack-virtual-modules: 0.6.2
|
||||
|
||||
unplugin@2.3.11:
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
@@ -14574,12 +14534,11 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1)):
|
||||
vite-tsconfig-paths@6.1.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1)):
|
||||
dependencies:
|
||||
debug: 4.3.7
|
||||
debug: 4.4.3
|
||||
globrex: 0.1.2
|
||||
tsconfck: 3.0.3(typescript@5.9.3)
|
||||
optionalDependencies:
|
||||
vite: 7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
|
||||
"github.com/enfein/mieru/v3/pkg/metrics"
|
||||
)
|
||||
|
||||
@@ -100,6 +101,12 @@ type BlockCipher interface {
|
||||
|
||||
// SetBlockContext sets the BlockContext.
|
||||
SetBlockContext(bc BlockContext)
|
||||
|
||||
// NoncePattern returns a copy of NoncePattern associated with the cipher block.
|
||||
NoncePattern() *appctlpb.NoncePattern
|
||||
|
||||
// SetNoncePattern sets the NoncePattern associated with the cipher block.
|
||||
SetNoncePattern(pattern *appctlpb.NoncePattern)
|
||||
}
|
||||
|
||||
// BlockContext contains optional context associated to a cipher block.
|
||||
|
||||
@@ -22,8 +22,10 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
|
||||
"github.com/enfein/mieru/v3/pkg/common"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -52,6 +54,7 @@ type AEADBlockCipher struct {
|
||||
implicitNonce []byte
|
||||
mu sync.Mutex
|
||||
ctx BlockContext
|
||||
noncePattern *appctlpb.NoncePattern
|
||||
}
|
||||
|
||||
// newAESGCMBlockCipher creates a new AES-GCM cipher with the supplied key.
|
||||
@@ -261,6 +264,7 @@ func (c *AEADBlockCipher) Clone() BlockCipher {
|
||||
copy(newCipher.implicitNonce, c.implicitNonce)
|
||||
}
|
||||
newCipher.ctx = c.ctx
|
||||
newCipher.noncePattern = proto.Clone(c.noncePattern).(*appctlpb.NoncePattern)
|
||||
return newCipher
|
||||
}
|
||||
|
||||
@@ -291,6 +295,18 @@ func (c *AEADBlockCipher) SetBlockContext(bc BlockContext) {
|
||||
c.ctx = bc
|
||||
}
|
||||
|
||||
func (c *AEADBlockCipher) NoncePattern() *appctlpb.NoncePattern {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return proto.Clone(c.noncePattern).(*appctlpb.NoncePattern)
|
||||
}
|
||||
|
||||
func (c *AEADBlockCipher) SetNoncePattern(pattern *appctlpb.NoncePattern) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.noncePattern = proto.Clone(pattern).(*appctlpb.NoncePattern)
|
||||
}
|
||||
|
||||
// newNonce generates a new nonce.
|
||||
func (c *AEADBlockCipher) newNonce() ([]byte, error) {
|
||||
nonce := make([]byte, c.NonceSize())
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"github.com/enfein/mieru/v3/pkg/mathext"
|
||||
"github.com/enfein/mieru/v3/pkg/sockopts"
|
||||
"github.com/enfein/mieru/v3/pkg/stderror"
|
||||
"github.com/enfein/mieru/v3/pkg/trafficpattern"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -53,6 +54,7 @@ type Mux struct {
|
||||
clientDNSConfig *apicommon.ClientDNSConfig
|
||||
streamListenerFactory apicommon.StreamListenerFactory
|
||||
packetListenerFactory apicommon.PacketListenerFactory
|
||||
trafficPattern *trafficpattern.Config
|
||||
chAccept chan net.Conn
|
||||
acceptHasErr atomic.Bool
|
||||
acceptErr chan error // this channel is closed when accept has error
|
||||
@@ -202,6 +204,15 @@ func (m *Mux) SetPacketListenerFactory(listenerFactory apicommon.PacketListenerF
|
||||
return m
|
||||
}
|
||||
|
||||
// SetTrafficPattern updates the traffic pattern configuration used by the mux.
|
||||
func (m *Mux) SetTrafficPattern(trafficPattern *trafficpattern.Config) *Mux {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.trafficPattern = trafficPattern
|
||||
log.Infof("Mux traffic pattern has been updated")
|
||||
return m
|
||||
}
|
||||
|
||||
// SetClientUserNamePassword panics if the mux is already started.
|
||||
func (m *Mux) SetClientUserNamePassword(username string, password []byte) *Mux {
|
||||
m.mu.Lock()
|
||||
|
||||
@@ -60,7 +60,7 @@ func (o *overrideSchema) Apply(mapping map[string]any) error {
|
||||
mapping["skip-cert-verify"] = *o.SkipCertVerify
|
||||
}
|
||||
if o.Interface != nil {
|
||||
mapping["interface"] = *o.Interface
|
||||
mapping["interface-name"] = *o.Interface
|
||||
}
|
||||
if o.RoutingMark != nil {
|
||||
mapping["routing-mark"] = *o.RoutingMark
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
PACKAGE_NAME=moe.nb4a
|
||||
VERSION_NAME=1.4.1
|
||||
VERSION_NAME=1.4.2
|
||||
PRE_VERSION_NAME=pre-1.4.2-20260202-1
|
||||
VERSION_CODE=45
|
||||
VERSION_CODE=46
|
||||
|
||||
@@ -38,6 +38,10 @@ local ss_method_list = {
|
||||
|
||||
local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" }
|
||||
|
||||
local header_type_list = {
|
||||
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
|
||||
}
|
||||
|
||||
local xray_version = api.get_app_version("xray")
|
||||
|
||||
o = s:option(ListValue, _n("protocol"), translate("Protocol"))
|
||||
@@ -545,17 +549,11 @@ o:depends({ [_n("tcp_guise")] = "http" })
|
||||
-- [[ mKCP部分 ]]--
|
||||
|
||||
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
|
||||
o:value("none", "none")
|
||||
o:value("header-srtp", "srtp")
|
||||
o:value("header-utp", "utp")
|
||||
o:value("header-wechat", "wechat-video")
|
||||
o:value("header-dtls", "dtls")
|
||||
o:value("header-wireguard", "wireguard")
|
||||
o:value("header-dns", "dns")
|
||||
for a, t in ipairs(header_type_list) do o:value(t) end
|
||||
o:depends({ [_n("transport")] = "mkcp" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
|
||||
o:depends({ [_n("mkcp_guise")] = "header-dns" })
|
||||
o:depends({ [_n("mkcp_guise")] = "dns" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
|
||||
o.default = "1350"
|
||||
@@ -720,7 +718,7 @@ o = s:option(Value, _n("xudp_concurrency"), translate("XUDP Mux concurrency"))
|
||||
o.default = 8
|
||||
o:depends({ [_n("mux")] = true })
|
||||
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
--[[tcpMptcp]]
|
||||
|
||||
+2
-3
@@ -45,9 +45,8 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
|
||||
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o:value("false")
|
||||
o:value("true")
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
|
||||
o.default = 0
|
||||
|
||||
@@ -46,9 +46,8 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
|
||||
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o:value("false")
|
||||
o:value("true")
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
|
||||
o.default = 0
|
||||
|
||||
@@ -68,8 +68,7 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
|
||||
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o:value("false")
|
||||
o:value("true")
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
api.luci_types(arg[1], m, s, type_name, option_prefix)
|
||||
|
||||
+2
-3
@@ -32,9 +32,8 @@ o.datatype = "port"
|
||||
o = s:option(Value, _n("password"), translate("Password"))
|
||||
o.password = true
|
||||
|
||||
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o:value("false")
|
||||
o:value("true")
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
o = s:option(Flag, _n("tls"), translate("TLS"))
|
||||
o.default = 0
|
||||
|
||||
@@ -20,6 +20,10 @@ local x_ss_method_list = {
|
||||
"none", "plain", "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
local header_type_list = {
|
||||
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
|
||||
}
|
||||
|
||||
-- [[ Xray ]]
|
||||
|
||||
s.fields["type"]:value(type_name, "Xray")
|
||||
@@ -314,17 +318,11 @@ o:depends({ [_n("tcp_guise")] = "http" })
|
||||
-- [[ mKCP部分 ]]--
|
||||
|
||||
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
|
||||
o:value("none", "none")
|
||||
o:value("header-srtp", "srtp")
|
||||
o:value("header-utp", "utp")
|
||||
o:value("header-wechat", "wechat-video")
|
||||
o:value("header-dtls", "dtls")
|
||||
o:value("header-wireguard", "wireguard")
|
||||
o:value("header-dns", "dns")
|
||||
for a, t in ipairs(header_type_list) do o:value(t) end
|
||||
o:depends({ [_n("transport")] = "mkcp" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
|
||||
o:depends({ [_n("mkcp_guise")] = "header-dns" })
|
||||
o:depends({ [_n("mkcp_guise")] = "dns" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
|
||||
o.default = "1350"
|
||||
@@ -364,6 +362,10 @@ o = s:option(Flag, _n("acceptProxyProtocol"), translate("acceptProxyProtocol"),
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
-- [[ Fallback部分 ]]--
|
||||
o = s:option(Flag, _n("fallback"), translate("Fallback"))
|
||||
o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "raw" })
|
||||
|
||||
@@ -75,7 +75,7 @@ function gen_config(var)
|
||||
password = node.password,
|
||||
method = node.method,
|
||||
timeout = tonumber(node.timeout),
|
||||
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false,
|
||||
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false,
|
||||
reuse_port = true,
|
||||
tcp_tproxy = var["tcp_tproxy"] and true or nil
|
||||
}
|
||||
@@ -103,7 +103,7 @@ function gen_config(var)
|
||||
}
|
||||
},
|
||||
locals = {},
|
||||
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false
|
||||
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false
|
||||
}
|
||||
if local_socks_address and local_socks_port then
|
||||
table.insert(config.locals, {
|
||||
|
||||
@@ -87,7 +87,7 @@ function gen_config(var)
|
||||
no_delay = true,
|
||||
keep_alive = true,
|
||||
reuse_port = true,
|
||||
fast_open = (node.tcp_fast_open == "true") and true or false,
|
||||
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false,
|
||||
fast_open_qlen = 20
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,9 +286,11 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
finalmask = (node.transport == "mkcp") and {
|
||||
udp = (function()
|
||||
local t = {}
|
||||
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
|
||||
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
|
||||
if node.mkcp_guise and node.mkcp_guise ~= "none" then
|
||||
local g = { type = node.mkcp_guise }
|
||||
if node.mkcp_guise == "header-dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
|
||||
local g = { type = map[node.mkcp_guise] }
|
||||
if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
|
||||
g.settings = { domain = node.mkcp_domain }
|
||||
end
|
||||
t[#t + 1] = g
|
||||
@@ -597,9 +599,11 @@ function gen_config_server(node)
|
||||
finalmask = (node.transport == "mkcp") and {
|
||||
udp = (function()
|
||||
local t = {}
|
||||
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
|
||||
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
|
||||
if node.mkcp_guise and node.mkcp_guise ~= "none" then
|
||||
local g = { type = node.mkcp_guise }
|
||||
if node.mkcp_guise == "header-dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
|
||||
local g = { type = map[node.mkcp_guise] }
|
||||
if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
|
||||
g.settings = { domain = node.mkcp_domain }
|
||||
end
|
||||
t[#t + 1] = g
|
||||
@@ -613,6 +617,7 @@ function gen_config_server(node)
|
||||
end)()
|
||||
} or nil,
|
||||
sockopt = {
|
||||
tcpFastOpen = (node.tcp_fast_open == "1") and true or nil,
|
||||
acceptProxyProtocol = (node.acceptProxyProtocol and node.acceptProxyProtocol == "1") and true or false
|
||||
}
|
||||
}
|
||||
|
||||
+35
-8
@@ -243,6 +243,9 @@ local current_node = map:get(section)
|
||||
}
|
||||
|
||||
var params = "";
|
||||
|
||||
opt.get(dom_prefix + "tcp_fast_open")?.checked && (params += "&tfo=1");
|
||||
|
||||
var v_plugin_dom = opt.get(dom_prefix + "plugin");
|
||||
if (v_plugin_dom) {
|
||||
var v_plugin = v_plugin_dom.value;
|
||||
@@ -322,6 +325,8 @@ local current_node = map:get(section)
|
||||
params += opt.query("ech", dom_prefix + "ech_config");
|
||||
}
|
||||
|
||||
opt.get(dom_prefix + "uot")?.checked && (params += "&udp=1");
|
||||
|
||||
if (opt.get(dom_prefix + "shadowtls")?.checked) {
|
||||
let st_plugin_str = "";
|
||||
let st_version = opt.get(dom_prefix + "shadowtls_version")?.value;
|
||||
@@ -418,6 +423,9 @@ local current_node = map:get(section)
|
||||
info.tls = "tls";
|
||||
info.sni = opt.get(dom_prefix + "tls_serverName").value;
|
||||
}
|
||||
|
||||
opt.get(dom_prefix + "tcp_fast_open")?.checked && (info.tfo = "1");
|
||||
|
||||
url = b64EncodeUnicode(JSON.stringify(info));
|
||||
} else if ((v_type === "sing-box" || v_type === "Xray") && opt.get(dom_prefix + "protocol").value === "vless") {
|
||||
protocol = "vless";
|
||||
@@ -502,6 +510,8 @@ local current_node = map:get(section)
|
||||
params += opt.query("ech", dom_prefix + "ech_config");
|
||||
}
|
||||
|
||||
opt.get(dom_prefix + "tcp_fast_open")?.checked && (params += "&tfo=1");
|
||||
|
||||
params += "#" + encodeURI(v_alias.value);
|
||||
if (params[0] == "&") {
|
||||
params = params.substring(1);
|
||||
@@ -567,6 +577,9 @@ local current_node = map:get(section)
|
||||
params += opt.query("vcn", dom_prefix + "tls_CertByName");
|
||||
params += opt.query("ech", dom_prefix + "ech_config");
|
||||
}
|
||||
|
||||
opt.get(dom_prefix + "tcp_fast_open")?.checked && (params += "&tfo=1");
|
||||
|
||||
params += "#" + encodeURI(v_alias.value);
|
||||
if (params[0] == "&") {
|
||||
params = params.substring(1);
|
||||
@@ -944,11 +957,14 @@ local current_node = map:get(section)
|
||||
pluginOpts = pluginParams.join(";");
|
||||
}
|
||||
|
||||
if (has_xray && ((ss_type !== "xray" && ss_type !== "sing-box" && queryParam.type) || ss_type == "xray" || queryParam.type == "xhttp")) {
|
||||
const needUpgrade = ss_type !== "Xray" && ss_type !== "sing-box" &&
|
||||
queryParam.type && queryParam.type !== "tcp" &&
|
||||
queryParam.headerType && queryParam.headerType !== "none";
|
||||
if (has_xray && (ss_type == "xray" || needUpgrade || queryParam.type === "xhttp")) {
|
||||
dom_prefix = "xray_"
|
||||
opt.set('type', "Xray");
|
||||
opt.set(dom_prefix + 'protocol', "shadowsocks");
|
||||
} else if (has_singbox && ((ss_type !== "xray" && ss_type !== "sing-box" && queryParam.type) || ss_type == "sing-box")) {
|
||||
} else if (has_singbox && (ss_type == "sing-box" || needUpgrade)) {
|
||||
dom_prefix = "singbox_"
|
||||
opt.set('type', "sing-box");
|
||||
opt.set(dom_prefix + 'protocol', "shadowsocks");
|
||||
@@ -977,6 +993,7 @@ local current_node = map:get(section)
|
||||
opt.set(dom_prefix + 'password', password || "");
|
||||
opt.set(dom_prefix + 'method', method || "");
|
||||
opt.set(dom_prefix + 'ss_method', method || "");
|
||||
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
|
||||
if (plugin && plugin != "none") {
|
||||
plugin = (plugin === "simple-obfs") ? "obfs-local" : plugin;
|
||||
opt.set(dom_prefix + 'plugin_enabled', true);
|
||||
@@ -1114,6 +1131,8 @@ local current_node = map:get(section)
|
||||
opt.set(dom_prefix + 'use_xhttp_extra', !!queryParam.extra);
|
||||
opt.set(dom_prefix + 'xhttp_extra', queryParam.extra || "");
|
||||
}
|
||||
|
||||
opt.set(dom_prefix + 'uot', queryParam.udp);
|
||||
|
||||
if (queryParam["shadow-tls"]) {
|
||||
//解析SS Shadow-TLS 插件参数
|
||||
@@ -1273,6 +1292,8 @@ local current_node = map:get(section)
|
||||
opt.set(dom_prefix + 'tls_allowInsecure', false);
|
||||
}
|
||||
|
||||
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
|
||||
|
||||
if (m.hash) {
|
||||
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
|
||||
}
|
||||
@@ -1367,6 +1388,8 @@ local current_node = map:get(section)
|
||||
} else if (ssm.net === "grpc") {
|
||||
opt.set(dom_prefix + 'grpc_serviceName', ssm.path);
|
||||
}
|
||||
|
||||
opt.set(dom_prefix + 'tcp_fast_open', ssm.tfo);
|
||||
}
|
||||
if (ssu[0] === "vless") {
|
||||
if (vless_type == "sing-box" && has_singbox) {
|
||||
@@ -1518,6 +1541,8 @@ local current_node = map:get(section)
|
||||
opt.set(dom_prefix + 'httpupgrade_path', queryParam.path || "");
|
||||
}
|
||||
|
||||
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
|
||||
|
||||
if (m.hash) {
|
||||
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
|
||||
}
|
||||
@@ -1697,12 +1722,14 @@ local current_node = map:get(section)
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const fromUrlCache = JSON.parse(sessionStorage.getItem("fromUrl"));
|
||||
if (fromUrlCache && fromUrlCache.savetime && (Date.now() - fromUrlCache.timestamp) < fromUrlCache.savetime) {
|
||||
fromUrl(null, fromUrlCache.urlname, fromUrlCache.sid, fromUrlCache)
|
||||
} else {
|
||||
sessionStorage.removeItem("fromUrl");
|
||||
}
|
||||
setTimeout(function () {
|
||||
const fromUrlCache = JSON.parse(sessionStorage.getItem("fromUrl"));
|
||||
if (fromUrlCache && fromUrlCache.savetime && (Date.now() - fromUrlCache.timestamp) < fromUrlCache.savetime) {
|
||||
fromUrl(null, fromUrlCache.urlname, fromUrlCache.sid, fromUrlCache)
|
||||
} else {
|
||||
sessionStorage.removeItem("fromUrl");
|
||||
}
|
||||
}, 500);
|
||||
})
|
||||
|
||||
//]]></script>
|
||||
|
||||
@@ -620,6 +620,8 @@ local function processData(szType, content, add_mode, group)
|
||||
result.tls = "0"
|
||||
end
|
||||
|
||||
result.tcp_fast_open = info.tfo
|
||||
|
||||
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
|
||||
log("跳过节点:" .. result.remarks ..",因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式,需更换Xray。")
|
||||
return nil
|
||||
@@ -723,13 +725,17 @@ local function processData(szType, content, add_mode, group)
|
||||
|
||||
result.method = method
|
||||
result.password = password
|
||||
result.tcp_fast_open = params.tfo
|
||||
|
||||
if has_xray and (result.type ~= 'Xray' and result.type ~= 'sing-box' and params.type) then
|
||||
result.type = 'Xray'
|
||||
result.protocol = 'shadowsocks'
|
||||
elseif has_singbox and (result.type ~= 'Xray' and result.type ~= 'sing-box' and params.type) then
|
||||
result.type = 'sing-box'
|
||||
result.protocol = 'shadowsocks'
|
||||
local need_upgrade = (result.type ~= "Xray" and result.type ~= "sing-box")
|
||||
and (params.type and params.type ~= "tcp")
|
||||
and (params.headerType and params.headerType ~= "none")
|
||||
if has_xray and (need_upgrade or params.type == "xhttp") then
|
||||
result.type = "Xray"
|
||||
result.protocol = "shadowsocks"
|
||||
elseif has_singbox and need_upgrade then
|
||||
result.type = "sing-box"
|
||||
result.protocol = "shadowsocks"
|
||||
end
|
||||
|
||||
if result.plugin then
|
||||
@@ -892,7 +898,8 @@ local function processData(szType, content, add_mode, group)
|
||||
else
|
||||
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
|
||||
end
|
||||
else
|
||||
result.uot = params.udp
|
||||
elseif (params.type ~= "tcp" and params.type ~= "raw") and (params.headerType and params.headerType ~= "none") then
|
||||
result.error_msg = "请更换Xray或Sing-Box来支持SS更多的传输方式."
|
||||
end
|
||||
end
|
||||
@@ -1091,6 +1098,7 @@ local function processData(szType, content, add_mode, group)
|
||||
end
|
||||
|
||||
result.alpn = params.alpn
|
||||
result.tcp_fast_open = params.tfo
|
||||
|
||||
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
|
||||
log("跳过节点:" .. result.remarks ..",因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式,需更换Xray。")
|
||||
@@ -1281,6 +1289,8 @@ local function processData(szType, content, add_mode, group)
|
||||
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
|
||||
end
|
||||
|
||||
result.tcp_fast_open = params.tfo
|
||||
|
||||
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
|
||||
log("跳过节点:" .. result.remarks ..",因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式,需更换Xray。")
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
# PassWall2
|
||||
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
[](https://openwrt.org/)
|
||||
[](https://github.com/openwrt/luci)
|
||||
|
||||
PassWall2 is a powerful LuCI web interface application for OpenWrt that provides advanced proxy and VPN functionality. It's a comprehensive solution for network traffic management, proxy services, and access control on OpenWrt-based routers.
|
||||
|
||||
|
||||
|
||||
|
||||
## 🛠️ Installation
|
||||
|
||||
### Method 1: Using OpenWrt Package Manager
|
||||
|
||||
1. **Update package lists:**
|
||||
```bash
|
||||
opkg update
|
||||
```
|
||||
|
||||
2. **Install PassWall2:**
|
||||
```bash
|
||||
opkg install luci-app-passwall2
|
||||
```
|
||||
|
||||
3. **Restart LuCI:**
|
||||
```bash
|
||||
/etc/init.d/rpcd restart
|
||||
```
|
||||
|
||||
### Method 2: Manual Installation
|
||||
|
||||
1. **Download the package:**
|
||||
```bash
|
||||
wget https://github.com/Openwrt-Passwall/openwrt-passwall2/releases/latest/download/luci-app-passwall2_*.ipk
|
||||
```
|
||||
|
||||
2. **Install the package:**
|
||||
```bash
|
||||
opkg install luci-app-passwall2_*.ipk
|
||||
```
|
||||
<details>
|
||||
|
||||
<summary>📋 System Requirements </summary>
|
||||
|
||||
### OpenWrt Version
|
||||
- OpenWrt 21.02 or later
|
||||
- LuCI 19.07 or later
|
||||
|
||||
### Hardware Requirements
|
||||
- Minimum 64MB RAM (128MB recommended)
|
||||
- Sufficient storage for packages (varies by protocol selection)
|
||||
- Network interface support for transparent proxy
|
||||
|
||||
### Dependencies
|
||||
The following packages are automatically installed based on your configuration:
|
||||
- `coreutils`, `curl`, `ip-full`, `libuci-lua`, `lua`, `luci-compat`
|
||||
- Protocol-specific packages (selected during installation)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>⚙️ Configuration </summary>
|
||||
|
||||
### Basic Setup
|
||||
|
||||
1. **Access LuCI Interface:**
|
||||
- Navigate to `Services` → `PassWall2` in your OpenWrt web interface
|
||||
|
||||
2. **Add Your First Node:**
|
||||
- Go to `Node List` → `Add Node`
|
||||
- Select your protocol (e.g., Shadowsocks, V2Ray, etc.)
|
||||
- Fill in server details (address, port, password, encryption)
|
||||
|
||||
3. **Configure Basic Settings:**
|
||||
- Go to `Basic Settings`
|
||||
- Select your default node
|
||||
- Configure DNS settings
|
||||
- Enable transparent proxy
|
||||
|
||||
4. **Apply Configuration:**
|
||||
- Click `Save & Apply`
|
||||
- Wait for services to start
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>🚀 Features</summary>
|
||||
|
||||
### Multi-Protocol Proxy Support
|
||||
- **Shadowsocks** (Libev & Rust implementations)
|
||||
- **V2Ray/Xray** with full protocol support
|
||||
- **Trojan** and **Trojan-Go**
|
||||
- **NaiveProxy** for advanced obfuscation
|
||||
- **Hysteria** and **Hysteria2** for high-performance UDP transport
|
||||
- **Sing-Box** with modern proxy features
|
||||
- **ShadowsocksR** legacy support
|
||||
- **WireGuard** integration
|
||||
|
||||
### Advanced Traffic Management
|
||||
- **Load Balancing**: Distribute traffic across multiple nodes
|
||||
- **URL Testing**: Automatically test and select optimal nodes
|
||||
- **Smart Routing**: Domain-based and geo-based routing rules
|
||||
- **DNS Manipulation**: Advanced DNS filtering and manipulation
|
||||
- **Traffic Sniffing**: Protocol detection and classification
|
||||
|
||||
### Node Management
|
||||
- **Subscription Support**: Import nodes from subscription URLs
|
||||
- **QR Code Generation**: Generate and scan QR codes for node sharing
|
||||
- **Node Testing**: Built-in latency and connectivity testing
|
||||
- **Health Checks**: Automatic node health monitoring
|
||||
- **Failover Support**: Automatic failover to backup nodes
|
||||
|
||||
### Access Control
|
||||
- **Per-Device Rules**: Configure proxy settings per device
|
||||
- **Domain Filtering**: Whitelist/blacklist domains
|
||||
- **IP Filtering**: IP-based access control
|
||||
- **Interface Control**: Route traffic based on network interfaces
|
||||
- **Time-based Rules**: Schedule proxy usage by time
|
||||
|
||||
### Server-Side Support
|
||||
- **Multi-User Server**: Host proxy services with user management
|
||||
- **Protocol Support**: All client protocols available as servers
|
||||
- **User Management**: Create and manage server users
|
||||
- **Traffic Monitoring**: Monitor server usage and statistics
|
||||
|
||||
### Advanced Features
|
||||
- **Multi-WAN Support**: Advanced routing for multi-WAN setups
|
||||
- **IPv6 Support**: Full IPv6 transparency and proxy support
|
||||
- **Transparent Proxy**: Transparent proxy for entire network
|
||||
- **Socks5/HTTP Proxy**: Local proxy server support
|
||||
- **NAT/Firewall Integration**: Seamless integration with OpenWrt firewall
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>🔧 Advanced Configuration</summary>
|
||||
|
||||
#### Load Balancing
|
||||
1. Create multiple nodes of the same type
|
||||
2. Go to `Node List` → `Add Node` → `Load Balancing`
|
||||
3. Select nodes to include in the load balancer
|
||||
4. Configure balancing strategy and health checks
|
||||
|
||||
#### Access Control
|
||||
1. Go to `Access Control`
|
||||
2. Add devices by MAC address or IP range
|
||||
3. Configure proxy rules for each device
|
||||
4. Set up domain and IP filtering
|
||||
|
||||
#### DNS Configuration
|
||||
1. Go to `Basic Settings` → `DNS Settings`
|
||||
2. Configure direct and remote DNS servers
|
||||
3. Set up DNS filtering rules
|
||||
4. Enable DNS over HTTPS if desired
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>📚 Supported Protocols</summary>
|
||||
|
||||
### Shadowsocks
|
||||
- **Libev**: Lightweight implementation
|
||||
- **Rust**: Modern, high-performance implementation
|
||||
- **Plugins**: Simple-obfs, v2ray-plugin support
|
||||
- **Encryption**: All standard encryption methods
|
||||
|
||||
### V2Ray/Xray
|
||||
- **Protocols**: VMess, VLESS, Trojan, Shadowsocks
|
||||
- **Transports**: TCP, mKCP, WebSocket, HTTP/2, QUIC
|
||||
- **Security**: TLS, XTLS support
|
||||
- **Features**: Routing, DNS,流量控制
|
||||
|
||||
### Trojan
|
||||
- **Standard Trojan**: Basic trojan protocol
|
||||
- **Trojan-Go**: Enhanced with additional features
|
||||
- **TLS Support**: Full TLS certificate support
|
||||
- **Obfuscation**: Built-in traffic obfuscation
|
||||
|
||||
### Hysteria
|
||||
- **Hysteria 1**: UDP-based transport protocol
|
||||
- **Hysteria 2**: Improved version with better performance
|
||||
- **Obfuscation**: Built-in traffic obfuscation
|
||||
- **UDP Optimization**: Optimized for poor network conditions
|
||||
|
||||
### Sing-Box
|
||||
- **Modern Architecture**: Latest proxy technology
|
||||
- **Protocol Support**: All major proxy protocols
|
||||
- **Performance**: High-performance implementation
|
||||
- **Features**: Advanced routing and filtering
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>🌐 Language Support</summary>
|
||||
|
||||
PassWall2 supports multiple languages:
|
||||
- 🇨🇳 Chinese (Simplified/Traditional)
|
||||
- 🇮🇷 Persian_farsi (soon)
|
||||
|
||||
Language files are located in `luci-app-passwall2/po/` directory.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>🔧 Troubleshooting</summary>
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Service Won't Start
|
||||
1. Check system logs: `logread | grep passwall2`
|
||||
2. Verify node configuration
|
||||
3. Check available memory and storage
|
||||
4. Ensure required packages are installed
|
||||
|
||||
#### DNS Issues
|
||||
1. Verify DNS server configuration
|
||||
2. Check DNS filtering rules
|
||||
3. Test with different DNS servers
|
||||
4. Clear DNS cache if needed
|
||||
|
||||
#### Connection Problems
|
||||
1. Test node connectivity
|
||||
2. Check firewall rules
|
||||
3. Verify transparent proxy settings
|
||||
4. Test with different protocols
|
||||
|
||||
#### Performance Issues
|
||||
1. Monitor system resources
|
||||
2. Check node health status
|
||||
3. Adjust connection limits
|
||||
4. Optimize routing rules
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable debug logging:
|
||||
1. Go to `Other Settings`
|
||||
2. Enable debug mode
|
||||
3. Check logs in `/tmp/log/passwall2.log`
|
||||
|
||||
### Log Locations
|
||||
- Main log: `/tmp/log/passwall2.log`
|
||||
- Server log: `/tmp/log/passwall2_server.log`
|
||||
- Temporary files: `/tmp/etc/passwall2_tmp/`
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>🙏 Acknowledgments</summary>
|
||||
|
||||
- OpenWrt community for the excellent platform
|
||||
- V2Ray/Xray project for the core proxy technology
|
||||
- All contributors and testers
|
||||
- The open-source community
|
||||
|
||||
</details>
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
|
||||
---
|
||||
|
||||
**Note**: This software is intended for legal use only. Users are responsible for complying with all applicable laws and regulations in their jurisdiction.
|
||||
|
||||
|
||||
## Stargazers over time
|
||||
[](https://starchart.cc/Openwrt-Passwall/openwrt-passwall2)
|
||||
+24
-35
@@ -87,13 +87,6 @@ if data.node.type == "Xray" then
|
||||
o:value("linear")
|
||||
end
|
||||
|
||||
o = add_option(Flag, "preproxy_enabled", translate("Preproxy") .. " " .. translate("Main switch"))
|
||||
|
||||
main_node = add_option(ListValue, "main_node", string.format('<a style="color:red">%s</a>', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not."))
|
||||
add_depends(main_node, {["preproxy_enabled"] = true})
|
||||
main_node.template = appname .. "/cbi/nodes_listvalue"
|
||||
main_node.group = {}
|
||||
|
||||
o = add_option(Flag, "fakedns", '<a style="color:#FF8C00">FakeDNS</a>' .. " " .. translate("Main switch"), translate("Use FakeDNS work in the domain that proxy.") .. "<br>" ..
|
||||
translate("Suitable scenarios for let the node servers get the target domain names.") .. "<br>" ..
|
||||
translate("Such as: DNS unlocking of streaming media, reducing DNS query latency, etc."))
|
||||
@@ -174,70 +167,66 @@ o.remove = function(self, section)
|
||||
return m:del(current_node_id, shunt_rules[section]["_fakedns_option"])
|
||||
end
|
||||
|
||||
o = s2:option(ListValue, "_proxy_tag", string.format('<a style="color:red">%s</a>', translate("Preproxy")))
|
||||
--TODO Choose any node as a pre-proxy. Instead of main node.
|
||||
o.template = appname .. "/cbi/nodes_listvalue"
|
||||
o.group = {"",""}
|
||||
o:value("", translate("Close (Not use)"))
|
||||
o:value("main", translate("Use preproxy node"))
|
||||
o.cfgvalue = function(self, section)
|
||||
proxy_tag_node = s2:option(ListValue, "_proxy_tag", string.format('<a style="color:red" title="%s">%s</a>',
|
||||
translate("Set the node to be used as a pre-proxy.") .. "\n" .. translate("Each rule has a separate switch that controls whether this rule uses the pre-proxy or not."),
|
||||
translate("Preproxy")))
|
||||
proxy_tag_node.template = appname .. "/cbi/nodes_listvalue"
|
||||
proxy_tag_node.group = {""}
|
||||
proxy_tag_node:value("", translate("Close (Not use)"))
|
||||
proxy_tag_node.cfgvalue = function(self, section)
|
||||
return m:get(current_node_id, shunt_rules[section]["_proxy_tag_option"])
|
||||
end
|
||||
o.write = function(self, section, value)
|
||||
proxy_tag_node.write = function(self, section, value)
|
||||
return m:set(current_node_id, shunt_rules[section]["_proxy_tag_option"], value)
|
||||
end
|
||||
o.remove = function(self, section)
|
||||
proxy_tag_node.remove = function(self, section)
|
||||
return m:del(current_node_id, shunt_rules[section]["_proxy_tag_option"])
|
||||
end
|
||||
|
||||
if data.socks_list then
|
||||
for k, v in pairs(data.socks_list) do
|
||||
main_node:value(v.id, v.remark)
|
||||
main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
|
||||
_node:value(v.id, v.remark)
|
||||
_node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
|
||||
proxy_tag_node:value(v.id, v.remark)
|
||||
proxy_tag_node.group[#proxy_tag_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
if data.urltest_list then
|
||||
for k, v in pairs(data.urltest_list) do
|
||||
main_node:value(v.id, v.remark)
|
||||
main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
|
||||
_node:value(v.id, v.remark)
|
||||
_node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
|
||||
proxy_tag_node:value(v.id, v.remark)
|
||||
proxy_tag_node.group[#proxy_tag_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
if data.balancing_list then
|
||||
for k, v in pairs(data.balancing_list) do
|
||||
main_node:value(v.id, v.remark)
|
||||
main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
|
||||
_node:value(v.id, v.remark)
|
||||
_node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
|
||||
proxy_tag_node:value(v.id, v.remark)
|
||||
proxy_tag_node.group[#proxy_tag_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
if data.iface_list then
|
||||
for k, v in pairs(data.iface_list) do
|
||||
main_node:value(v.id, v.remark)
|
||||
main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
|
||||
_node:value(v.id, v.remark)
|
||||
_node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
|
||||
proxy_tag_node:value(v.id, v.remark)
|
||||
proxy_tag_node.group[#proxy_tag_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
if data.normal_list then
|
||||
for k, v in pairs(data.normal_list) do
|
||||
main_node:value(v.id, v.remark)
|
||||
main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
|
||||
_node:value(v.id, v.remark)
|
||||
_node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
|
||||
if #main_node.keylist > 0 then
|
||||
main_node.default = main_node.keylist[1]
|
||||
proxy_tag_node:value(v.id, v.remark)
|
||||
proxy_tag_node.group[#proxy_tag_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
|
||||
local footer = Template(appname .. "/include/shunt_options")
|
||||
|
||||
+9
-8
@@ -17,6 +17,15 @@ m:append(header)
|
||||
m:append(Template(appname .. "/cbi/nodes_multivalue_com"))
|
||||
m:append(Template(appname .. "/cbi/nodes_listvalue_com"))
|
||||
|
||||
groups = {}
|
||||
m.uci:foreach(appname, "nodes", function(s)
|
||||
if s[".name"] ~= arg[1] then
|
||||
if s.group and s.group ~= "" then
|
||||
groups[s.group] = true
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
s = m:section(NamedSection, arg[1], "nodes", "")
|
||||
s.addremove = false
|
||||
s.dynamic = false
|
||||
@@ -33,14 +42,6 @@ o.rmempty = false
|
||||
o = s:option(Value, "group", translate("Group Name"))
|
||||
o.default = ""
|
||||
o:value("", translate("default"))
|
||||
local groups = {}
|
||||
m.uci:foreach(appname, "nodes", function(s)
|
||||
if s[".name"] ~= arg[1] then
|
||||
if s.group and s.group ~= "" then
|
||||
groups[s.group] = true
|
||||
end
|
||||
end
|
||||
end)
|
||||
for k, v in pairs(groups) do
|
||||
o:value(k)
|
||||
end
|
||||
|
||||
+45
-16
@@ -38,6 +38,10 @@ local ss_method_list = {
|
||||
|
||||
local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" }
|
||||
|
||||
local header_type_list = {
|
||||
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
|
||||
}
|
||||
|
||||
local xray_version = api.get_app_version("xray")
|
||||
|
||||
o = s:option(ListValue, _n("protocol"), translate("Protocol"))
|
||||
@@ -128,8 +132,14 @@ m.uci:foreach(appname, "socks", function(s)
|
||||
end)
|
||||
|
||||
if load_balancing_options then -- [[ Load balancing Start ]]
|
||||
o = s:option(MultiValue, _n("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://xtls.github.io/config/routing.html#balancerobject'>document</a>"))
|
||||
o = s:option(ListValue, _n("node_add_mode"), translate("Node Addition Method"))
|
||||
o:depends({ [_n("protocol")] = "_balancing" })
|
||||
o.default = "manual"
|
||||
o:value("manual", translate("Manual"))
|
||||
o:value("batch", translate("Batch"))
|
||||
|
||||
o = s:option(MultiValue, _n("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://xtls.github.io/config/routing.html#balancerobject'>document</a>"))
|
||||
o:depends({ [_n("node_add_mode")] = "manual" })
|
||||
o.widget = "checkbox"
|
||||
o.template = appname .. "/cbi/nodes_multivalue"
|
||||
o.group = {}
|
||||
@@ -166,6 +176,21 @@ if load_balancing_options then -- [[ Load balancing Start ]]
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(MultiValue, _n("node_group"), translate("Select Group"))
|
||||
o:depends({ [_n("node_add_mode")] = "batch" })
|
||||
o.widget = "checkbox"
|
||||
o:value("default", translate("default"))
|
||||
for k, v in pairs(groups) do
|
||||
o:value(api.UrlEncode(k), k)
|
||||
end
|
||||
|
||||
o = s:option(Value, _n("node_match_rule"), translate("Node Matching Rules"))
|
||||
o:depends({ [_n("node_add_mode")] = "batch" })
|
||||
local descrStr = "Example: <code>^A && B && !C && D$</code><br>"
|
||||
descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
|
||||
descrStr = descrStr .. "Conditions are joined by <code>&&</code>, and their order does not affect the result."
|
||||
o.description = translate(descrStr)
|
||||
|
||||
o = s:option(ListValue, _n("balancingStrategy"), translate("Balancing Strategy"))
|
||||
o:depends({ [_n("protocol")] = "_balancing" })
|
||||
o:value("random")
|
||||
@@ -517,17 +542,11 @@ o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
-- [[ mKCP ]]--
|
||||
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
|
||||
o:value("none", "none")
|
||||
o:value("header-srtp", "srtp")
|
||||
o:value("header-utp", "utp")
|
||||
o:value("header-wechat", "wechat-video")
|
||||
o:value("header-dtls", "dtls")
|
||||
o:value("header-wireguard", "wireguard")
|
||||
o:value("header-dns", "dns")
|
||||
for a, t in ipairs(header_type_list) do o:value(t) end
|
||||
o:depends({ [_n("transport")] = "mkcp" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
|
||||
o:depends({ [_n("mkcp_guise")] = "header-dns" })
|
||||
o:depends({ [_n("mkcp_guise")] = "dns" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
|
||||
o.default = "1350"
|
||||
@@ -692,7 +711,7 @@ o = s:option(Value, _n("xudp_concurrency"), translate("XUDP Mux concurrency"))
|
||||
o.default = 8
|
||||
o:depends({ [_n("mux")] = true })
|
||||
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
--[[tcpMptcp]]
|
||||
@@ -724,12 +743,22 @@ o2:depends({ [_n("chain_proxy")] = "2" })
|
||||
o2.template = appname .. "/cbi/nodes_listvalue"
|
||||
o2.group = {}
|
||||
|
||||
for k, v in pairs(nodes_list) do
|
||||
if v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then
|
||||
o1:value(v.id, v.remark)
|
||||
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
o2:value(v.id, v.remark)
|
||||
o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
for k, v in pairs(socks_list) do
|
||||
o1:value(v.id, v.remark)
|
||||
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e[".name"] ~= arg[1] then
|
||||
if e.protocol ~= "_shunt" and e.protocol ~= "_iface" then
|
||||
o1:value(e[".name"], e["remark"])
|
||||
o1.group[#o1.group+1] = (e["group"] and e["group"] ~= "") and e["group"] or translate("default")
|
||||
end
|
||||
if not e.protocol:find("_") then
|
||||
-- Landing Node not support use special node.
|
||||
o2:value(e[".name"], e["remark"])
|
||||
o2.group[#o2.group+1] = (e["group"] and e["group"] ~= "") and e["group"] or translate("default")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+38
-7
@@ -136,8 +136,14 @@ m.uci:foreach(appname, "socks", function(s)
|
||||
end)
|
||||
|
||||
if load_urltest_options then -- [[ URLTest Start ]]
|
||||
o = s:option(MultiValue, _n("urltest_node"), translate("URLTest node list"), translate("List of nodes to test, <a target='_blank' href='https://sing-box.sagernet.org/configuration/outbound/urltest'>document</a>"))
|
||||
o = s:option(ListValue, _n("node_add_mode"), translate("Node Addition Method"))
|
||||
o:depends({ [_n("protocol")] = "_urltest" })
|
||||
o.default = "manual"
|
||||
o:value("manual", translate("Manual"))
|
||||
o:value("batch", translate("Batch"))
|
||||
|
||||
o = s:option(MultiValue, _n("urltest_node"), translate("URLTest node list"), translate("List of nodes to test, <a target='_blank' href='https://sing-box.sagernet.org/configuration/outbound/urltest'>document</a>"))
|
||||
o:depends({ [_n("node_add_mode")] = "manual" })
|
||||
o.widget = "checkbox"
|
||||
o.template = appname .. "/cbi/nodes_multivalue"
|
||||
o.group = {}
|
||||
@@ -174,6 +180,21 @@ if load_urltest_options then -- [[ URLTest Start ]]
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(MultiValue, _n("node_group"), translate("Select Group"))
|
||||
o:depends({ [_n("node_add_mode")] = "batch" })
|
||||
o.widget = "checkbox"
|
||||
o:value("default", translate("default"))
|
||||
for k, v in pairs(groups) do
|
||||
o:value(api.UrlEncode(k), k)
|
||||
end
|
||||
|
||||
o = s:option(Value, _n("node_match_rule"), translate("Node Matching Rules"))
|
||||
o:depends({ [_n("node_add_mode")] = "batch" })
|
||||
local descrStr = "Example: <code>^A && B && !C && D$</code><br>"
|
||||
descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
|
||||
descrStr = descrStr .. "Conditions are joined by <code>&&</code>, and their order does not affect the result."
|
||||
o.description = translate(descrStr)
|
||||
|
||||
o = s:option(Value, _n("urltest_url"), translate("Probe URL"))
|
||||
o:depends({ [_n("protocol")] = "_urltest" })
|
||||
o:value("https://cp.cloudflare.com/", "Cloudflare")
|
||||
@@ -826,12 +847,22 @@ o2:depends({ [_n("chain_proxy")] = "2" })
|
||||
o2.template = appname .. "/cbi/nodes_listvalue"
|
||||
o2.group = {}
|
||||
|
||||
for k, v in pairs(nodes_list) do
|
||||
if v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then
|
||||
o1:value(v.id, v.remark)
|
||||
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
o2:value(v.id, v.remark)
|
||||
o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
for k, v in pairs(socks_list) do
|
||||
o1:value(v.id, v.remark)
|
||||
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e[".name"] ~= arg[1] then
|
||||
if e.protocol ~= "_shunt" and e.protocol ~= "_iface" then
|
||||
o1:value(e[".name"], e["remark"])
|
||||
o1.group[#o1.group+1] = (e["group"] and e["group"] ~= "") and e["group"] or translate("default")
|
||||
end
|
||||
if not e.protocol:find("_") then
|
||||
-- Landing Node not support use special node.
|
||||
o2:value(e[".name"], e["remark"])
|
||||
o2.group[#o2.group+1] = (e["group"] and e["group"] ~= "") and e["group"] or translate("default")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+2
-3
@@ -41,9 +41,8 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
|
||||
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o:value("false")
|
||||
o:value("true")
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
|
||||
o.default = 0
|
||||
|
||||
@@ -42,9 +42,8 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
|
||||
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o:value("false")
|
||||
o:value("true")
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
|
||||
o.default = 0
|
||||
|
||||
@@ -64,8 +64,7 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
|
||||
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o:value("false")
|
||||
o:value("true")
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
api.luci_types(arg[1], m, s, type_name, option_prefix)
|
||||
|
||||
+10
-8
@@ -18,6 +18,10 @@ local x_ss_method_list = {
|
||||
"none", "plain", "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
local header_type_list = {
|
||||
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
|
||||
}
|
||||
|
||||
-- [[ Xray ]]
|
||||
|
||||
s.fields["type"]:value(type_name, "Xray")
|
||||
@@ -318,17 +322,11 @@ o:depends({ [_n("tcp_guise")] = "http" })
|
||||
-- [[ mKCP ]]--
|
||||
|
||||
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
|
||||
o:value("none", "none")
|
||||
o:value("header-srtp", "srtp")
|
||||
o:value("header-utp", "utp")
|
||||
o:value("header-wechat", "wechat-video")
|
||||
o:value("header-dtls", "dtls")
|
||||
o:value("header-wireguard", "wireguard")
|
||||
o:value("header-dns", "dns")
|
||||
for a, t in ipairs(header_type_list) do o:value(t) end
|
||||
o:depends({ [_n("transport")] = "mkcp" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
|
||||
o:depends({ [_n("mkcp_guise")] = "header-dns" })
|
||||
o:depends({ [_n("mkcp_guise")] = "dns" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
|
||||
o.default = "1350"
|
||||
@@ -368,6 +366,10 @@ o = s:option(Flag, _n("acceptProxyProtocol"), translate("acceptProxyProtocol"),
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
-- [[ Fallback ]]--
|
||||
o = s:option(Flag, _n("fallback"), translate("Fallback"))
|
||||
o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "raw" })
|
||||
|
||||
@@ -164,6 +164,18 @@ function base64Encode(text)
|
||||
return result
|
||||
end
|
||||
|
||||
function UrlEncode(szText)
|
||||
return szText:gsub("([^%w%-_%.%~])", function(c)
|
||||
return string.format("%%%02X", string.byte(c))
|
||||
end)
|
||||
end
|
||||
|
||||
function UrlDecode(szText)
|
||||
return szText and szText:gsub("%+", " "):gsub("%%(%x%x)", function(h)
|
||||
return string.char(tonumber(h, 16))
|
||||
end) or nil
|
||||
end
|
||||
|
||||
-- Extract the domain name and port from the URL (no IP address).
|
||||
function get_domain_port_from_url(url)
|
||||
local scheme, domain, port = string.match(url, "^(https?)://([%w%.%-]+):?(%d*)")
|
||||
@@ -1427,3 +1439,50 @@ function apply_redirect(m)
|
||||
sys.call("/bin/rm -f " .. tmp_uci_file)
|
||||
end
|
||||
end
|
||||
|
||||
function match_node_rule(name, rule)
|
||||
if not name then return false end
|
||||
if not rule or rule == "" then return true end
|
||||
-- split rule by &&
|
||||
local function split_and(expr)
|
||||
local t = {}
|
||||
for part in expr:gmatch("[^&]+") do
|
||||
part = part:gsub("^%s+", ""):gsub("%s+$", "")
|
||||
if part ~= "" then
|
||||
table.insert(t, part)
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
-- match single condition
|
||||
local function match_cond(str, cond)
|
||||
if cond == "" then
|
||||
return true
|
||||
end
|
||||
-- exclude: !xxx
|
||||
if cond:sub(1, 1) == "!" then
|
||||
local k = cond:sub(2)
|
||||
if k == "" then return true end
|
||||
return not str:find(k, 1, true)
|
||||
end
|
||||
-- prefix: ^xxx
|
||||
if cond:sub(1, 1) == "^" then
|
||||
local k = cond:sub(2)
|
||||
return str:sub(1, #k) == k
|
||||
end
|
||||
-- suffix: xxx$
|
||||
if cond:sub(-1) == "$" then
|
||||
local k = cond:sub(1, -2)
|
||||
return str:sub(-#k) == k
|
||||
end
|
||||
-- contains
|
||||
return str:find(cond, 1, true) ~= nil
|
||||
end
|
||||
-- AND logic
|
||||
for _, cond in ipairs(split_and(rule)) do
|
||||
if not match_cond(name, cond) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -71,7 +71,7 @@ function gen_config(var)
|
||||
password = node.password,
|
||||
method = node.method,
|
||||
timeout = tonumber(node.timeout),
|
||||
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false,
|
||||
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false,
|
||||
reuse_port = true
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ function gen_config(var)
|
||||
}
|
||||
},
|
||||
locals = {},
|
||||
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false
|
||||
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false
|
||||
}
|
||||
if local_socks_address and local_socks_port then
|
||||
table.insert(config.locals, {
|
||||
|
||||
@@ -1095,9 +1095,10 @@ function gen_config(var)
|
||||
local socks_node = uci:get_all(appname, socks_id) or nil
|
||||
if socks_node then
|
||||
if not remarks then
|
||||
remarks = "Socks_" .. socks_node.port
|
||||
remarks = socks_node.port
|
||||
end
|
||||
result = {
|
||||
[".name"] = "Socksid_" .. socks_id,
|
||||
remarks = remarks,
|
||||
type = "sing-box",
|
||||
protocol = "socks",
|
||||
@@ -1109,7 +1110,43 @@ function gen_config(var)
|
||||
return result
|
||||
end
|
||||
|
||||
function gen_urltest(_node)
|
||||
local nodes_list = {}
|
||||
function get_urltest_batch_nodes(_node)
|
||||
if #nodes_list == 0 then
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e.node_type == "normal" and (not e.chain_proxy or e.chain_proxy == "") then
|
||||
nodes_list[#nodes_list + 1] = {
|
||||
id = e[".name"],
|
||||
remarks = e["remarks"],
|
||||
group = e["group"]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
if not _node.node_group or _node.node_group == "" then return {} end
|
||||
local nodes = {}
|
||||
for g in _node.node_group:gmatch("%S+") do
|
||||
g = api.UrlDecode(g)
|
||||
for k, v in pairs(nodes_list) do
|
||||
local gn = (v.group and v.group ~= "") and v.group or "default"
|
||||
if gn == g and api.match_node_rule(v.remarks, _node.node_match_rule) then
|
||||
nodes[#nodes + 1] = v.id
|
||||
end
|
||||
end
|
||||
end
|
||||
return nodes
|
||||
end
|
||||
|
||||
function get_node_by_id(node_id)
|
||||
if not node_id or node_id == "" or node_id == "nil" then return nil end
|
||||
if node_id:find("Socks_") then
|
||||
return gen_socks_config_node(node_id)
|
||||
else
|
||||
return uci:get_all(appname, node_id)
|
||||
end
|
||||
end
|
||||
|
||||
function gen_urltest_outbound(_node)
|
||||
local urltest_id = _node[".name"]
|
||||
local urltest_tag = "urltest-" .. urltest_id
|
||||
-- existing urltest
|
||||
@@ -1119,7 +1156,13 @@ function gen_config(var)
|
||||
end
|
||||
end
|
||||
-- new urltest
|
||||
local ut_nodes = _node.urltest_node
|
||||
local ut_nodes
|
||||
if _node.node_add_mode and _node.node_add_mode == "batch" then
|
||||
ut_nodes = get_urltest_batch_nodes(_node)
|
||||
else
|
||||
ut_nodes = _node.urltest_node
|
||||
end
|
||||
if #ut_nodes == 0 then return nil end
|
||||
local valid_nodes = {}
|
||||
for i = 1, #ut_nodes do
|
||||
local ut_node_id = ut_nodes[i]
|
||||
@@ -1133,21 +1176,9 @@ function gen_config(var)
|
||||
end
|
||||
end
|
||||
if is_new_ut_node then
|
||||
local ut_node
|
||||
if ut_node_id:find("Socks_") then
|
||||
ut_node = gen_socks_config_node(ut_node_id)
|
||||
else
|
||||
ut_node = uci:get_all(appname, ut_node_id)
|
||||
end
|
||||
if ut_node then
|
||||
local outbound = gen_outbound(flag, ut_node, ut_node_tag, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run })
|
||||
if outbound then
|
||||
if ut_node.remarks then
|
||||
outbound.tag = outbound.tag .. ":" .. ut_node.remarks
|
||||
end
|
||||
table.insert(outbounds, outbound)
|
||||
valid_nodes[#valid_nodes + 1] = outbound.tag
|
||||
end
|
||||
local outboundTag = gen_outbound_get_tag(flag, ut_node_id, ut_node_tag, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run })
|
||||
if outboundTag then
|
||||
valid_nodes[#valid_nodes + 1] = outboundTag
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1162,8 +1193,7 @@ function gen_config(var)
|
||||
idle_timeout = (api.format_go_time(_node.urltest_idle_timeout) ~= "0s") and api.format_go_time(_node.urltest_idle_timeout) or "30m",
|
||||
interrupt_exist_connections = (_node.urltest_interrupt_exist_connections == "true" or _node.urltest_interrupt_exist_connections == "1") and true or false
|
||||
}
|
||||
table.insert(outbounds, outbound)
|
||||
return urltest_tag
|
||||
return outbound
|
||||
end
|
||||
|
||||
function set_outbound_detour(node, outbound, outbounds_table, shunt_rule_name)
|
||||
@@ -1197,9 +1227,16 @@ function gen_config(var)
|
||||
if outbound["_flag_proxy_tag"] then
|
||||
--Ignore
|
||||
else
|
||||
local preproxy_node = uci:get_all(appname, node.preproxy_node)
|
||||
local preproxy_node = get_node_by_id(node.preproxy_node)
|
||||
if preproxy_node then
|
||||
local preproxy_outbound = gen_outbound(node[".name"], preproxy_node)
|
||||
local preproxy_outbound
|
||||
if preproxy_node.protocol == "_urltest" then
|
||||
if preproxy_node.urltest_node then
|
||||
preproxy_outbound = gen_urltest_outbound(preproxy_node)
|
||||
end
|
||||
else
|
||||
preproxy_outbound = gen_outbound(node[".name"], preproxy_node)
|
||||
end
|
||||
if preproxy_outbound then
|
||||
preproxy_outbound.tag = preproxy_node[".name"]
|
||||
if preproxy_node.remarks then
|
||||
@@ -1214,7 +1251,13 @@ function gen_config(var)
|
||||
end
|
||||
end
|
||||
if node.chain_proxy == "2" and node.to_node then
|
||||
local to_node = uci:get_all(appname, node.to_node)
|
||||
local to_node = get_node_by_id(node.to_node)
|
||||
if to_node then
|
||||
-- Landing Node not support use special node.
|
||||
if to_node.protocol:find("_") then
|
||||
to_node = nil
|
||||
end
|
||||
end
|
||||
if to_node then
|
||||
local to_outbound
|
||||
if to_node.type ~= "sing-box" then
|
||||
@@ -1265,122 +1308,90 @@ function gen_config(var)
|
||||
return default_outTag, last_insert_outbound
|
||||
end
|
||||
|
||||
function gen_outbound_get_tag(flag, node_id, tag, proxy_table)
|
||||
if not node_id or node_id == "nil" then return nil end
|
||||
local node
|
||||
if type(node_id) == "string" then
|
||||
node = get_node_by_id(node_id)
|
||||
elseif type(node_id) == "table" then
|
||||
node = node_id
|
||||
end
|
||||
if node then
|
||||
if node.protocol == "_iface" then
|
||||
if node.iface then
|
||||
local outbound = {
|
||||
tag = tag,
|
||||
type = "direct",
|
||||
bind_interface = node.iface,
|
||||
routing_mark = 255,
|
||||
}
|
||||
table.insert(outbounds, outbound)
|
||||
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface))
|
||||
return outbound.tag
|
||||
end
|
||||
return nil
|
||||
end
|
||||
if proxy_table.chain_proxy == "1" or proxy_table.chain_proxy == "2" then
|
||||
node.chain_proxy = proxy_table.chain_proxy
|
||||
node.preproxy_node = proxy_table.chain_proxy == "1" and proxy_table.preproxy_node
|
||||
node.to_node = proxy_table.chain_proxy == "2" and proxy_table.to_node
|
||||
proxy_table.chain_proxy = nil
|
||||
proxy_table.preproxy_node = nil
|
||||
proxy_table.to_node = nil
|
||||
end
|
||||
local outbound
|
||||
if node.protocol == "_urltest" then
|
||||
if node.urltest_node then
|
||||
outbound = gen_urltest_outbound(node)
|
||||
end
|
||||
else
|
||||
outbound = gen_outbound(flag, node, tag, proxy_table)
|
||||
end
|
||||
if outbound then
|
||||
if node.remarks then
|
||||
outbound.tag = outbound.tag .. ":" .. node.remarks
|
||||
end
|
||||
local default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds)
|
||||
table.insert(outbounds, outbound)
|
||||
if last_insert_outbound then
|
||||
table.insert(outbounds, last_insert_outbound)
|
||||
end
|
||||
return default_outbound_tag
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rules = {}
|
||||
|
||||
if node.protocol == "_shunt" then
|
||||
local preproxy_rule_name = node.preproxy_enabled == "1" and "main" or nil
|
||||
local preproxy_tag = preproxy_rule_name
|
||||
local preproxy_node_id = preproxy_rule_name and node["main_node"] or nil
|
||||
|
||||
inner_fakedns = node.fakedns or "0"
|
||||
|
||||
local function gen_shunt_node(rule_name, _node_id)
|
||||
if not rule_name then return nil end
|
||||
if not _node_id then _node_id = node[rule_name] end
|
||||
local rule_outboundTag
|
||||
if _node_id == "_direct" then
|
||||
rule_outboundTag = "direct"
|
||||
return "direct"
|
||||
elseif _node_id == "_blackhole" then
|
||||
rule_outboundTag = "block"
|
||||
return "block"
|
||||
elseif _node_id == "_default" and rule_name ~= "default" then
|
||||
rule_outboundTag = "default"
|
||||
elseif _node_id and _node_id:find("Socks_") then
|
||||
local socks_node = gen_socks_config_node(_node_id)
|
||||
local _outbound = gen_outbound(flag, socks_node, rule_name)
|
||||
if _outbound then
|
||||
table.insert(outbounds, _outbound)
|
||||
rule_outboundTag = _outbound.tag
|
||||
end
|
||||
return "default"
|
||||
elseif _node_id then
|
||||
local _node = uci:get_all(appname, _node_id)
|
||||
if not _node then return nil end
|
||||
|
||||
if api.is_normal_node(_node) then
|
||||
local use_proxy = preproxy_tag and node[rule_name .. "_proxy_tag"] == preproxy_rule_name and _node_id ~= preproxy_node_id
|
||||
local copied_outbound
|
||||
for index, value in ipairs(outbounds) do
|
||||
if value["_id"] == _node_id and value["_flag_proxy_tag"] == (use_proxy and preproxy_tag or nil) then
|
||||
copied_outbound = api.clone(value)
|
||||
break
|
||||
end
|
||||
end
|
||||
if copied_outbound then
|
||||
copied_outbound.tag = rule_name .. ":" .. _node.remarks
|
||||
table.insert(outbounds, copied_outbound)
|
||||
rule_outboundTag = copied_outbound.tag
|
||||
else
|
||||
if use_proxy then
|
||||
local pre_proxy = nil
|
||||
if _node.type ~= "sing-box" then
|
||||
pre_proxy = true
|
||||
end
|
||||
if pre_proxy then
|
||||
local new_port = api.get_new_port()
|
||||
table.insert(inbounds, {
|
||||
type = "direct",
|
||||
tag = "proxy_" .. rule_name,
|
||||
listen = "127.0.0.1",
|
||||
listen_port = new_port,
|
||||
override_address = _node.address,
|
||||
override_port = tonumber(_node.port),
|
||||
})
|
||||
if _node.tls_serverName == nil then
|
||||
_node.tls_serverName = _node.address
|
||||
end
|
||||
_node.address = "127.0.0.1"
|
||||
_node.port = new_port
|
||||
table.insert(rules, 1, {
|
||||
inbound = {"proxy_" .. rule_name},
|
||||
outbound = preproxy_tag,
|
||||
})
|
||||
end
|
||||
end
|
||||
local proxy_table = {
|
||||
tag = use_proxy and preproxy_tag or nil,
|
||||
run_socks_instance = not no_run
|
||||
}
|
||||
if not proxy_table.tag then
|
||||
if singbox_settings.fragment == "1" then
|
||||
proxy_table.fragment = true
|
||||
end
|
||||
if singbox_settings.record_fragment == "1" then
|
||||
proxy_table.record_fragment = true
|
||||
end
|
||||
end
|
||||
local _outbound = gen_outbound(flag, _node, rule_name, proxy_table)
|
||||
if _outbound then
|
||||
_outbound.tag = _outbound.tag .. ":" .. _node.remarks
|
||||
rule_outboundTag, last_insert_outbound = set_outbound_detour(_node, _outbound, outbounds, rule_name)
|
||||
table.insert(outbounds, _outbound)
|
||||
if last_insert_outbound then
|
||||
table.insert(outbounds, last_insert_outbound)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif _node.protocol == "_urltest" then
|
||||
rule_outboundTag = gen_urltest(_node)
|
||||
elseif _node.protocol == "_iface" then
|
||||
if _node.iface then
|
||||
local _outbound = {
|
||||
type = "direct",
|
||||
tag = rule_name .. ":" .. _node.remarks,
|
||||
bind_interface = _node.iface,
|
||||
routing_mark = 255,
|
||||
}
|
||||
table.insert(outbounds, _outbound)
|
||||
rule_outboundTag = _outbound.tag
|
||||
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, _node.iface))
|
||||
end
|
||||
local proxy_table = {
|
||||
fragment = singbox_settings.fragment == "1",
|
||||
record_fragment = singbox_settings.record_fragment == "1",
|
||||
run_socks_instance = not no_run,
|
||||
}
|
||||
local preproxy_node_id = node[rule_name .. "_proxy_tag"]
|
||||
if preproxy_node_id == _node_id then preproxy_node_id = nil end
|
||||
if preproxy_node_id then
|
||||
proxy_table.chain_proxy = "2"
|
||||
proxy_table.to_node = _node_id
|
||||
return gen_outbound_get_tag(flag, preproxy_node_id, rule_name, proxy_table)
|
||||
else
|
||||
return gen_outbound_get_tag(flag, _node_id, rule_name, proxy_table)
|
||||
end
|
||||
end
|
||||
return rule_outboundTag
|
||||
end
|
||||
|
||||
if preproxy_tag and preproxy_node_id then
|
||||
local preproxy_outboundTag = gen_shunt_node(preproxy_rule_name, preproxy_node_id)
|
||||
if preproxy_outboundTag then
|
||||
preproxy_tag = preproxy_outboundTag
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--default_node
|
||||
@@ -1582,32 +1593,12 @@ function gen_config(var)
|
||||
table.insert(rules, rule)
|
||||
end
|
||||
end)
|
||||
elseif node.protocol == "_urltest" then
|
||||
if node.urltest_node then
|
||||
COMMON.default_outbound_tag = gen_urltest(node)
|
||||
end
|
||||
elseif node.protocol == "_iface" then
|
||||
if node.iface then
|
||||
local outbound = {
|
||||
type = "direct",
|
||||
tag = node.remarks or node_id,
|
||||
bind_interface = node.iface,
|
||||
routing_mark = 255,
|
||||
}
|
||||
table.insert(outbounds, outbound)
|
||||
COMMON.default_outbound_tag = outbound.tag
|
||||
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface))
|
||||
end
|
||||
else
|
||||
local outbound = gen_outbound(flag, node, nil, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run })
|
||||
if outbound then
|
||||
outbound.tag = outbound.tag .. ":" .. node.remarks
|
||||
COMMON.default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds)
|
||||
table.insert(outbounds, outbound)
|
||||
if last_insert_outbound then
|
||||
table.insert(outbounds, last_insert_outbound)
|
||||
end
|
||||
end
|
||||
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node, nil, {
|
||||
fragment = singbox_settings.fragment == "1" or nil,
|
||||
record_fragment = singbox_settings.record_fragment == "1" or nil,
|
||||
run_socks_instance = not no_run
|
||||
})
|
||||
end
|
||||
|
||||
for index, value in ipairs(rules) do
|
||||
@@ -1730,7 +1721,7 @@ function gen_config(var)
|
||||
|
||||
local default_dns_flag = "remote"
|
||||
if node_id and redir_port then
|
||||
local node = uci:get_all(appname, node_id)
|
||||
local node = get_node_by_id(node_id)
|
||||
if node.protocol == "_shunt" then
|
||||
if node.default_node == "_direct" then
|
||||
default_dns_flag = "direct"
|
||||
|
||||
@@ -58,7 +58,7 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
run_socks_instance = proxy_table.run_socks_instance
|
||||
end
|
||||
|
||||
if node.type ~= "Xray" then
|
||||
if node.type ~= "Xray" or node.protocol == "_balancing" then
|
||||
local relay_port = node.port
|
||||
local new_port = api.get_new_port()
|
||||
local config_file = string.format("%s_%s_%s.json", flag, tag, new_port)
|
||||
@@ -282,9 +282,11 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
finalmask = (node.transport == "mkcp") and {
|
||||
udp = (function()
|
||||
local t = {}
|
||||
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
|
||||
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
|
||||
if node.mkcp_guise and node.mkcp_guise ~= "none" then
|
||||
local g = { type = node.mkcp_guise }
|
||||
if node.mkcp_guise == "header-dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
|
||||
local g = { type = map[node.mkcp_guise] }
|
||||
if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
|
||||
g.settings = { domain = node.mkcp_domain }
|
||||
end
|
||||
t[#t + 1] = g
|
||||
@@ -592,9 +594,11 @@ function gen_config_server(node)
|
||||
finalmask = (node.transport == "mkcp") and {
|
||||
udp = (function()
|
||||
local t = {}
|
||||
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
|
||||
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
|
||||
if node.mkcp_guise and node.mkcp_guise ~= "none" then
|
||||
local g = { type = node.mkcp_guise }
|
||||
if node.mkcp_guise == "header-dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
|
||||
local g = { type = map[node.mkcp_guise] }
|
||||
if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
|
||||
g.settings = { domain = node.mkcp_domain }
|
||||
end
|
||||
t[#t + 1] = g
|
||||
@@ -608,6 +612,7 @@ function gen_config_server(node)
|
||||
end)()
|
||||
} or nil,
|
||||
sockopt = {
|
||||
tcpFastOpen = (node.tcp_fast_open == "1") and true or nil,
|
||||
acceptProxyProtocol = (node.acceptProxyProtocol and node.acceptProxyProtocol == "1") and true or false
|
||||
}
|
||||
}
|
||||
@@ -757,9 +762,10 @@ function gen_config(var)
|
||||
local socks_node = uci:get_all(appname, socks_id) or nil
|
||||
if socks_node then
|
||||
if not remarks then
|
||||
remarks = "Socks_" .. socks_node.port
|
||||
remarks = socks_node.port
|
||||
end
|
||||
result = {
|
||||
[".name"] = "Socksid_" .. socks_id,
|
||||
remarks = remarks,
|
||||
type = "Xray",
|
||||
protocol = "socks",
|
||||
@@ -772,15 +778,51 @@ function gen_config(var)
|
||||
return result
|
||||
end
|
||||
|
||||
function gen_loopback(outboundTag, dst_node_id)
|
||||
if not outboundTag then return nil end
|
||||
local inboundTag = dst_node_id and "loop-in-" .. dst_node_id or outboundTag .. "-lo"
|
||||
function get_node_by_id(node_id)
|
||||
if not node_id or node_id == "" or node_id == "nil" then return nil end
|
||||
if node_id:find("Socks_") then
|
||||
return gen_socks_config_node(node_id)
|
||||
else
|
||||
return uci:get_all(appname, node_id)
|
||||
end
|
||||
end
|
||||
|
||||
local nodes_list = {}
|
||||
function get_balancer_batch_nodes(_node)
|
||||
if #nodes_list == 0 then
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e.node_type == "normal" and (not e.chain_proxy or e.chain_proxy == "") then
|
||||
nodes_list[#nodes_list + 1] = {
|
||||
id = e[".name"],
|
||||
remarks = e["remarks"],
|
||||
group = e["group"]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
if not _node.node_group or _node.node_group == "" then return {} end
|
||||
local nodes = {}
|
||||
for g in _node.node_group:gmatch("%S+") do
|
||||
g = api.UrlDecode(g)
|
||||
for k, v in pairs(nodes_list) do
|
||||
local gn = (v.group and v.group ~= "") and v.group or "default"
|
||||
if gn == g and api.match_node_rule(v.remarks, _node.node_match_rule) then
|
||||
nodes[#nodes + 1] = v.id
|
||||
end
|
||||
end
|
||||
end
|
||||
return nodes
|
||||
end
|
||||
|
||||
function gen_loopback(outbound_tag, loopback_dst)
|
||||
if not outbound_tag or outbound_tag == "" then return nil end
|
||||
local inbound_tag = loopback_dst and "lo-to-" .. loopback_dst or outbound_tag .. "-lo"
|
||||
table.insert(outbounds, {
|
||||
protocol = "loopback",
|
||||
tag = outboundTag,
|
||||
settings = { inboundTag = inboundTag }
|
||||
tag = outbound_tag,
|
||||
settings = { inboundTag = inbound_tag }
|
||||
})
|
||||
return inboundTag
|
||||
return inbound_tag
|
||||
end
|
||||
|
||||
function gen_balancer(_node, loopback_tag)
|
||||
@@ -796,7 +838,12 @@ function gen_config(var)
|
||||
end
|
||||
end
|
||||
-- new balancer
|
||||
local blc_nodes = _node.balancing_node
|
||||
local blc_nodes
|
||||
if _node.node_add_mode and _node.node_add_mode == "batch" then
|
||||
blc_nodes = get_balancer_batch_nodes(_node)
|
||||
else
|
||||
blc_nodes = _node.balancing_node
|
||||
end
|
||||
local valid_nodes = {}
|
||||
for i = 1, #blc_nodes do
|
||||
local blc_node_id = blc_nodes[i]
|
||||
@@ -810,21 +857,9 @@ function gen_config(var)
|
||||
end
|
||||
end
|
||||
if is_new_blc_node then
|
||||
local blc_node
|
||||
if blc_node_id:find("Socks_") then
|
||||
blc_node = gen_socks_config_node(blc_node_id)
|
||||
else
|
||||
blc_node = uci:get_all(appname, blc_node_id)
|
||||
end
|
||||
if blc_node then
|
||||
local outbound = gen_outbound(flag, blc_node, blc_node_tag, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run })
|
||||
if outbound then
|
||||
if blc_node.remarks then
|
||||
outbound.tag = outbound.tag .. ":" .. blc_node.remarks
|
||||
end
|
||||
table.insert(outbounds, outbound)
|
||||
valid_nodes[#valid_nodes + 1] = outbound.tag
|
||||
end
|
||||
local outboundTag = gen_outbound_get_tag(flag, blc_node_id, blc_node_tag, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.record_fragment == "1" or nil, run_socks_instance = not no_run })
|
||||
if outboundTag then
|
||||
valid_nodes[#valid_nodes + 1] = outboundTag
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -844,21 +879,12 @@ function gen_config(var)
|
||||
end
|
||||
end
|
||||
if is_new_node then
|
||||
local fallback_node
|
||||
if fallback_node_id:find("Socks_") then
|
||||
fallback_node = gen_socks_config_node(fallback_node_id)
|
||||
else
|
||||
fallback_node = uci:get_all(appname, fallback_node_id)
|
||||
end
|
||||
local fallback_node = get_node_by_id(fallback_node_id)
|
||||
if fallback_node then
|
||||
if fallback_node.protocol ~= "_balancing" then
|
||||
local outbound = gen_outbound(flag, fallback_node, fallback_node_id, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run })
|
||||
if outbound then
|
||||
if fallback_node.remarks then
|
||||
outbound.tag = outbound.tag .. ":" .. fallback_node.remarks
|
||||
end
|
||||
table.insert(outbounds, outbound)
|
||||
fallback_node_tag = outbound.tag
|
||||
local outboundTag = gen_outbound_get_tag(flag, fallback_node, fallback_node_id, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.record_fragment == "1" or nil, run_socks_instance = not no_run })
|
||||
if outboundTag then
|
||||
fallback_node_tag = outboundTag
|
||||
end
|
||||
else
|
||||
if gen_balancer(fallback_node) then
|
||||
@@ -923,7 +949,7 @@ function gen_config(var)
|
||||
if outbound["_flag_proxy_tag"] then
|
||||
--Ignore
|
||||
else
|
||||
local preproxy_node = uci:get_all(appname, node.preproxy_node)
|
||||
local preproxy_node = get_node_by_id(node.preproxy_node)
|
||||
if preproxy_node then
|
||||
local preproxy_outbound = gen_outbound(node[".name"], preproxy_node)
|
||||
if preproxy_outbound then
|
||||
@@ -943,7 +969,13 @@ function gen_config(var)
|
||||
end
|
||||
end
|
||||
if node.chain_proxy == "2" and node.to_node then
|
||||
local to_node = uci:get_all(appname, node.to_node)
|
||||
local to_node = get_node_by_id(node.to_node)
|
||||
if to_node then
|
||||
-- Landing Node not support use special node.
|
||||
if to_node.protocol:find("_") then
|
||||
to_node = nil
|
||||
end
|
||||
end
|
||||
if to_node then
|
||||
local to_outbound
|
||||
if to_node.type ~= "Xray" then
|
||||
@@ -996,18 +1028,66 @@ function gen_config(var)
|
||||
return default_outTag, last_insert_outbound
|
||||
end
|
||||
|
||||
function gen_outbound_get_tag(flag, node_id, tag, proxy_table)
|
||||
if not node_id or node_id == "nil" then return nil end
|
||||
local node
|
||||
if type(node_id) == "string" then
|
||||
node = get_node_by_id(node_id)
|
||||
elseif type(node_id) == "table" then
|
||||
node = node_id
|
||||
end
|
||||
if node then
|
||||
if node.protocol == "_iface" then
|
||||
if node.iface then
|
||||
local outbound = {
|
||||
tag = tag,
|
||||
protocol = "freedom",
|
||||
streamSettings = {
|
||||
sockopt = {
|
||||
mark = 255,
|
||||
interface = node.iface
|
||||
}
|
||||
}
|
||||
}
|
||||
table.insert(outbounds, outbound)
|
||||
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface))
|
||||
return outbound.tag
|
||||
end
|
||||
return nil
|
||||
end
|
||||
if proxy_table.chain_proxy == "1" or proxy_table.chain_proxy == "2" then
|
||||
node.chain_proxy = proxy_table.chain_proxy
|
||||
node.preproxy_node = proxy_table.chain_proxy == "1" and proxy_table.preproxy_node
|
||||
node.to_node = proxy_table.chain_proxy == "2" and proxy_table.to_node
|
||||
proxy_table.chain_proxy = nil
|
||||
proxy_table.preproxy_node = nil
|
||||
proxy_table.to_node = nil
|
||||
end
|
||||
local outbound = gen_outbound(flag, node, tag, proxy_table)
|
||||
if outbound then
|
||||
if node.remarks then
|
||||
outbound.tag = outbound.tag .. ":" .. node.remarks
|
||||
end
|
||||
local default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds)
|
||||
if tag == "default" then
|
||||
table.insert(outbounds, 1, outbound)
|
||||
else
|
||||
table.insert(outbounds, outbound)
|
||||
end
|
||||
if last_insert_outbound then
|
||||
table.insert(outbounds, last_insert_outbound)
|
||||
end
|
||||
return default_outbound_tag
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if node then
|
||||
if server_host and server_port then
|
||||
node.address = server_host
|
||||
node.port = server_port
|
||||
end
|
||||
if node.protocol == "_shunt" then
|
||||
local preproxy_rule_name = node.preproxy_enabled == "1" and "main" or nil
|
||||
local preproxy_tag = preproxy_rule_name
|
||||
local preproxy_node_id = node["main_node"]
|
||||
local preproxy_outbound_tag, preproxy_balancer_tag
|
||||
local preproxy_nodes
|
||||
|
||||
inner_fakedns = node.fakedns or "0"
|
||||
|
||||
local function gen_shunt_node(rule_name, _node_id)
|
||||
@@ -1017,144 +1097,25 @@ function gen_config(var)
|
||||
return "direct", nil
|
||||
elseif _node_id == "_blackhole" then
|
||||
return "blackhole", nil
|
||||
elseif _node_id == "_default" then
|
||||
elseif _node_id == "_default" and rule_name ~= "default" then
|
||||
return "default", nil
|
||||
elseif _node_id and _node_id:find("Socks_") then
|
||||
local socks_tag
|
||||
local socks_node = gen_socks_config_node(_node_id)
|
||||
local outbound = gen_outbound(flag, socks_node, rule_name)
|
||||
if outbound then
|
||||
if rule_name == "default" then
|
||||
table.insert(outbounds, 1, outbound)
|
||||
else
|
||||
table.insert(outbounds, outbound)
|
||||
end
|
||||
socks_tag = outbound.tag
|
||||
end
|
||||
return socks_tag, nil
|
||||
elseif _node_id then
|
||||
local _node = uci:get_all(appname, _node_id)
|
||||
if not _node then return nil, nil end
|
||||
|
||||
if api.is_normal_node(_node) then
|
||||
local use_proxy = preproxy_tag and node[rule_name .. "_proxy_tag"] == preproxy_rule_name and _node_id ~= preproxy_node_id
|
||||
if use_proxy and preproxy_balancer_tag and preproxy_nodes[_node_id] then use_proxy = false end
|
||||
local copied_outbound
|
||||
for index, value in ipairs(outbounds) do
|
||||
if value["_id"] == _node_id and value["_flag_proxy_tag"] == (use_proxy and preproxy_tag or nil) then
|
||||
copied_outbound = api.clone(value)
|
||||
break
|
||||
end
|
||||
end
|
||||
if copied_outbound then
|
||||
copied_outbound.tag = rule_name .. ":" .. _node.remarks
|
||||
table.insert(outbounds, copied_outbound)
|
||||
return copied_outbound.tag, nil
|
||||
else
|
||||
if use_proxy and _node.type ~= "Xray" then
|
||||
local new_port = api.get_new_port()
|
||||
table.insert(inbounds, {
|
||||
tag = "proxy_" .. rule_name,
|
||||
listen = "127.0.0.1",
|
||||
port = new_port,
|
||||
protocol = "dokodemo-door",
|
||||
settings = {network = "tcp,udp", address = _node.address, port = tonumber(_node.port)}
|
||||
})
|
||||
if _node.tls_serverName == nil then
|
||||
_node.tls_serverName = _node.address
|
||||
end
|
||||
_node.address = "127.0.0.1"
|
||||
_node.port = new_port
|
||||
table.insert(rules, 1, {
|
||||
inboundTag = {"proxy_" .. rule_name},
|
||||
outboundTag = not preproxy_balancer_tag and preproxy_tag or nil,
|
||||
balancerTag = preproxy_balancer_tag
|
||||
})
|
||||
end
|
||||
local proxy_table = {
|
||||
tag = use_proxy and preproxy_tag or nil,
|
||||
run_socks_instance = not no_run
|
||||
}
|
||||
if not proxy_table.tag then
|
||||
if xray_settings.fragment == "1" then
|
||||
proxy_table.fragment = true
|
||||
end
|
||||
if xray_settings.noise == "1" then
|
||||
proxy_table.noise = true
|
||||
end
|
||||
end
|
||||
local outbound = gen_outbound(flag, _node, rule_name, proxy_table)
|
||||
local outbound_tag
|
||||
if outbound then
|
||||
outbound.tag = outbound.tag .. ":" .. _node.remarks
|
||||
outbound_tag, last_insert_outbound = set_outbound_detour(_node, outbound, outbounds, rule_name)
|
||||
if rule_name == "default" then
|
||||
table.insert(outbounds, 1, outbound)
|
||||
else
|
||||
table.insert(outbounds, outbound)
|
||||
end
|
||||
if last_insert_outbound then
|
||||
table.insert(outbounds, last_insert_outbound)
|
||||
end
|
||||
end
|
||||
return outbound_tag, nil
|
||||
end
|
||||
elseif _node.protocol == "_balancing" then
|
||||
local blc_tag = gen_balancer(_node, rule_name)
|
||||
if rule_name == "default" then
|
||||
for i, ob in ipairs(outbounds) do
|
||||
if ob.protocol == "loopback" and ob.tag == "default" then
|
||||
if i > 1 then table.insert(outbounds, 1, table.remove(outbounds, i)) end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil, blc_tag
|
||||
elseif _node.protocol == "_iface" then
|
||||
local outbound_tag
|
||||
if _node.iface then
|
||||
local outbound = {
|
||||
protocol = "freedom",
|
||||
tag = rule_name,
|
||||
streamSettings = {
|
||||
sockopt = {
|
||||
mark = 255,
|
||||
interface = _node.iface
|
||||
}
|
||||
}
|
||||
}
|
||||
outbound_tag = outbound.tag
|
||||
if rule_name == "default" then
|
||||
table.insert(outbounds, 1, outbound)
|
||||
else
|
||||
table.insert(outbounds, outbound)
|
||||
end
|
||||
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, _node.iface))
|
||||
end
|
||||
return outbound_tag, nil
|
||||
local proxy_table = {
|
||||
fragment = xray_settings.fragment == "1",
|
||||
noise = xray_settings.noise == "1",
|
||||
run_socks_instance = not no_run,
|
||||
}
|
||||
local preproxy_node_id = node[rule_name .. "_proxy_tag"]
|
||||
if preproxy_node_id == _node_id then preproxy_node_id = nil end
|
||||
if preproxy_node_id then
|
||||
proxy_table.chain_proxy = "2"
|
||||
proxy_table.to_node = _node_id
|
||||
return gen_outbound_get_tag(flag, preproxy_node_id, rule_name, proxy_table), nil
|
||||
else
|
||||
return gen_outbound_get_tag(flag, _node_id, rule_name, proxy_table), nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if preproxy_tag and preproxy_node_id then
|
||||
preproxy_outbound_tag, preproxy_balancer_tag = gen_shunt_node(preproxy_rule_name, preproxy_node_id)
|
||||
if preproxy_balancer_tag then
|
||||
local _node_id = preproxy_node_id
|
||||
preproxy_nodes = {}
|
||||
while _node_id do
|
||||
_node = uci:get_all(appname, _node_id)
|
||||
if not _node then break end
|
||||
if _node.protocol ~= "_balancing" then
|
||||
preproxy_nodes[_node_id] = true
|
||||
break
|
||||
end
|
||||
local _blc_nodes = _node.balancing_node
|
||||
for i = 1, #_blc_nodes do preproxy_nodes[_blc_nodes[i]] = true end
|
||||
_node_id = _node.fallback_node
|
||||
end
|
||||
elseif preproxy_outbound_tag then
|
||||
preproxy_tag = preproxy_outbound_tag
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
--default_node
|
||||
@@ -1290,7 +1251,13 @@ function gen_config(var)
|
||||
rules = rules
|
||||
}
|
||||
elseif node.protocol == "_balancing" then
|
||||
if node.balancing_node then
|
||||
local blc_nodes
|
||||
if node.node_add_mode and node.node_add_mode == "batch" then
|
||||
blc_nodes = get_balancer_batch_nodes(node)
|
||||
else
|
||||
blc_nodes = node.balancing_node
|
||||
end
|
||||
if blc_nodes and #blc_nodes > 0 then
|
||||
local balancer_tag = gen_balancer(node)
|
||||
if balancer_tag then
|
||||
table.insert(rules, { network = "tcp,udp", balancerTag = balancer_tag })
|
||||
@@ -1301,31 +1268,13 @@ function gen_config(var)
|
||||
}
|
||||
COMMON.default_balancer_tag = balancer_tag
|
||||
end
|
||||
elseif node.protocol == "_iface" then
|
||||
if node.iface then
|
||||
local outbound = {
|
||||
protocol = "freedom",
|
||||
tag = node.remarks or node_id,
|
||||
streamSettings = {
|
||||
sockopt = {
|
||||
mark = 255,
|
||||
interface = node.iface
|
||||
}
|
||||
}
|
||||
}
|
||||
table.insert(outbounds, outbound)
|
||||
COMMON.default_outbound_tag = outbound.tag
|
||||
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface))
|
||||
end
|
||||
else
|
||||
local outbound = gen_outbound(flag, node, nil, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run })
|
||||
if outbound then
|
||||
outbound.tag = outbound.tag .. ":" .. node.remarks
|
||||
COMMON.default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds)
|
||||
table.insert(outbounds, outbound)
|
||||
if last_insert_outbound then
|
||||
table.insert(outbounds, last_insert_outbound)
|
||||
end
|
||||
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node, nil, {
|
||||
fragment = xray_settings.fragment == "1" or nil,
|
||||
noise = xray_settings.noise == "1" or nil,
|
||||
run_socks_instance = not no_run
|
||||
})
|
||||
if COMMON.default_outbound_tag then
|
||||
routing = {
|
||||
domainStrategy = "AsIs",
|
||||
domainMatcher = "hybrid",
|
||||
|
||||
+35
-9
@@ -239,6 +239,9 @@ local current_node = map:get(section)
|
||||
}
|
||||
|
||||
var params = "";
|
||||
|
||||
opt.get(dom_prefix + "tcp_fast_open")?.checked && (params += "&tfo=1");
|
||||
|
||||
var v_plugin_dom = opt.get(dom_prefix + "plugin");
|
||||
if (v_plugin_dom) {
|
||||
var v_plugin = v_plugin_dom.value;
|
||||
@@ -318,6 +321,8 @@ local current_node = map:get(section)
|
||||
params += opt.query("ech", dom_prefix + "ech_config");
|
||||
}
|
||||
|
||||
opt.get(dom_prefix + "uot")?.checked && (params += "&udp=1");
|
||||
|
||||
if (opt.get(dom_prefix + "shadowtls")?.checked) {
|
||||
let st_plugin_str = "";
|
||||
let st_version = opt.get(dom_prefix + "shadowtls_version")?.value;
|
||||
@@ -414,6 +419,9 @@ local current_node = map:get(section)
|
||||
info.tls = "tls";
|
||||
info.sni = opt.get(dom_prefix + "tls_serverName").value;
|
||||
}
|
||||
|
||||
opt.get(dom_prefix + "tcp_fast_open")?.checked && (info.tfo = "1");
|
||||
|
||||
url = b64EncodeUnicode(JSON.stringify(info));
|
||||
} else if ((v_type === "sing-box" || v_type === "Xray") && opt.get(dom_prefix + "protocol").value === "vless") {
|
||||
protocol = "vless";
|
||||
@@ -498,6 +506,8 @@ local current_node = map:get(section)
|
||||
params += opt.query("ech", dom_prefix + "ech_config");
|
||||
}
|
||||
|
||||
opt.get(dom_prefix + "tcp_fast_open")?.checked && (params += "&tfo=1");
|
||||
|
||||
params += "#" + encodeURI(v_alias.value);
|
||||
if (params[0] == "&") {
|
||||
params = params.substring(1);
|
||||
@@ -563,6 +573,9 @@ local current_node = map:get(section)
|
||||
params += opt.query("vcn", dom_prefix + "tls_CertByName");
|
||||
params += opt.query("ech", dom_prefix + "ech_config");
|
||||
}
|
||||
|
||||
opt.get(dom_prefix + "tcp_fast_open")?.checked && (params += "&tfo=1");
|
||||
|
||||
params += "#" + encodeURI(v_alias.value);
|
||||
if (params[0] == "&") {
|
||||
params = params.substring(1);
|
||||
@@ -940,11 +953,14 @@ local current_node = map:get(section)
|
||||
pluginOpts = pluginParams.join(";");
|
||||
}
|
||||
|
||||
if (has_xray && ((ss_type !== "xray" && ss_type !== "sing-box" && queryParam.type) || ss_type == "xray" || queryParam.type == "xhttp")) {
|
||||
const needUpgrade = ss_type !== "Xray" && ss_type !== "sing-box" &&
|
||||
queryParam.type && queryParam.type !== "tcp" &&
|
||||
queryParam.headerType && queryParam.headerType !== "none";
|
||||
if (has_xray && (ss_type == "xray" || needUpgrade || queryParam.type === "xhttp")) {
|
||||
dom_prefix = "xray_"
|
||||
opt.set('type', "Xray");
|
||||
opt.set(dom_prefix + 'protocol', "shadowsocks");
|
||||
} else if (has_singbox && ((ss_type !== "xray" && ss_type !== "sing-box" && queryParam.type) || ss_type == "sing-box")) {
|
||||
} else if (has_singbox && (ss_type == "sing-box" || needUpgrade)) {
|
||||
dom_prefix = "singbox_"
|
||||
opt.set('type', "sing-box");
|
||||
opt.set(dom_prefix + 'protocol', "shadowsocks");
|
||||
@@ -973,6 +989,7 @@ local current_node = map:get(section)
|
||||
opt.set(dom_prefix + 'password', password || "");
|
||||
opt.set(dom_prefix + 'method', method || "");
|
||||
opt.set(dom_prefix + 'ss_method', method || "");
|
||||
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
|
||||
if (plugin && plugin != "none") {
|
||||
plugin = (plugin === "simple-obfs") ? "obfs-local" : plugin;
|
||||
opt.set(dom_prefix + 'plugin_enabled', true);
|
||||
@@ -1110,6 +1127,8 @@ local current_node = map:get(section)
|
||||
opt.set(dom_prefix + 'use_xhttp_extra', !!queryParam.extra);
|
||||
opt.set(dom_prefix + 'xhttp_extra', queryParam.extra || "");
|
||||
}
|
||||
|
||||
opt.set(dom_prefix + 'uot', queryParam.udp);
|
||||
|
||||
if (queryParam["shadow-tls"]) {
|
||||
// Parsing SS Shadow-TLS plugin parameters
|
||||
@@ -1267,7 +1286,8 @@ local current_node = map:get(section)
|
||||
opt.set(dom_prefix + 'tls_allowInsecure', false);
|
||||
}
|
||||
|
||||
opt.set(dom_prefix + 'mux', queryParam.mux === '1');
|
||||
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
|
||||
|
||||
if (m.hash) {
|
||||
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
|
||||
}
|
||||
@@ -1362,6 +1382,8 @@ local current_node = map:get(section)
|
||||
} else if (ssm.net === "grpc") {
|
||||
opt.set(dom_prefix + 'grpc_serviceName', ssm.path);
|
||||
}
|
||||
|
||||
opt.set(dom_prefix + 'tcp_fast_open', ssm.tfo);
|
||||
}
|
||||
if (ssu[0] === "vless") {
|
||||
if (vless_type == "sing-box" && has_singbox) {
|
||||
@@ -1513,6 +1535,8 @@ local current_node = map:get(section)
|
||||
opt.set(dom_prefix + 'httpupgrade_path', queryParam.path || "");
|
||||
}
|
||||
|
||||
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
|
||||
|
||||
if (m.hash) {
|
||||
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
|
||||
}
|
||||
@@ -1692,12 +1716,14 @@ local current_node = map:get(section)
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const fromUrlCache = JSON.parse(sessionStorage.getItem("fromUrl"));
|
||||
if (fromUrlCache && fromUrlCache.savetime && (Date.now() - fromUrlCache.timestamp) < fromUrlCache.savetime) {
|
||||
fromUrl(null, fromUrlCache.urlname, fromUrlCache.sid, fromUrlCache)
|
||||
} else {
|
||||
sessionStorage.removeItem("fromUrl");
|
||||
}
|
||||
setTimeout(function () {
|
||||
const fromUrlCache = JSON.parse(sessionStorage.getItem("fromUrl"));
|
||||
if (fromUrlCache && fromUrlCache.savetime && (Date.now() - fromUrlCache.timestamp) < fromUrlCache.savetime) {
|
||||
fromUrl(null, fromUrlCache.urlname, fromUrlCache.sid, fromUrlCache)
|
||||
} else {
|
||||
sessionStorage.removeItem("fromUrl");
|
||||
}
|
||||
}, 500);
|
||||
})
|
||||
|
||||
//]]></script>
|
||||
|
||||
@@ -453,14 +453,11 @@ msgstr "پیشپروکسی"
|
||||
msgid "Preproxy Node"
|
||||
msgstr "گره پیشپروکسی"
|
||||
|
||||
msgid ""
|
||||
"Set the node to be used as a pre-proxy. Each rule (including <code>Default</"
|
||||
"code>) has a separate switch that controls whether this rule uses the pre-"
|
||||
"proxy or not."
|
||||
msgstr ""
|
||||
"گرهای را که باید به عنوان پیشپروکسی استفاده شود تنظیم کنید. هر قانون (شامل <code>Default</"
|
||||
"code>) دارای یک سوئیچ جداگانه است که کنترل میکند آیا این قانون از پیش-"
|
||||
"پروکسی استفاده کند یا خیر."
|
||||
msgid "Set the node to be used as a pre-proxy."
|
||||
msgstr "گره را طوری تنظیم کنید که به عنوان پیش پروکسی استفاده شود."
|
||||
|
||||
msgid "Each rule has a separate switch that controls whether this rule uses the pre-proxy or not."
|
||||
msgstr "هر قانون یک سوئیچ جداگانه دارد که کنترل میکند آیا این قانون از پیش-پروکسی استفاده میکند یا خیر."
|
||||
|
||||
msgid "Close (Not use)"
|
||||
msgstr "بستن (عدم استفاده)"
|
||||
@@ -474,12 +471,6 @@ msgstr "اتصال مستقیم"
|
||||
msgid "Blackhole (Block)"
|
||||
msgstr "سیاهچاله (مسدودسازی)"
|
||||
|
||||
msgid "Use preproxy node"
|
||||
msgstr "استفاده از گره پیشپروکسی"
|
||||
|
||||
msgid "Default Preproxy"
|
||||
msgstr "پیشپروکسی پیشفرض"
|
||||
|
||||
msgid "There are no available nodes, please add or subscribe nodes first."
|
||||
msgstr "هیچ گره در دسترس نیست، لطفاً ابتدا گرهها را اضافه یا مشترک شوید."
|
||||
|
||||
@@ -813,18 +804,6 @@ msgstr "سوئیچ بازگردانی"
|
||||
msgid "When detects main node is available, switch back to the main node."
|
||||
msgstr "زمانی که در دسترس بودن گره اصلی تشخیص داده شود، به گره اصلی بازگردانده میشود."
|
||||
|
||||
msgid "If the main node is shunt"
|
||||
msgstr "اگر گره اصلی شنت (Shunt) باشد"
|
||||
|
||||
msgid "Switch it"
|
||||
msgstr "سوئیچ کردن"
|
||||
|
||||
msgid "Applying to the default node"
|
||||
msgstr "اعمال بر روی گره پیشفرض"
|
||||
|
||||
msgid "Applying to the default preproxy node"
|
||||
msgstr "اعمال بر روی گره پیشپروکسی پیشفرض"
|
||||
|
||||
msgid "Add nodes to the standby node list by keywords"
|
||||
msgstr "افزودن گرهها به لیست گرههای آمادهبهکار با استفاده از کلمات کلیدی"
|
||||
|
||||
|
||||
@@ -367,6 +367,30 @@ msgstr "分流"
|
||||
msgid "Balancing"
|
||||
msgstr "负载均衡"
|
||||
|
||||
msgid "Node Addition Method"
|
||||
msgstr "节点添加方式"
|
||||
|
||||
msgid "Manual"
|
||||
msgstr "手动"
|
||||
|
||||
msgid "Batch"
|
||||
msgstr "批量"
|
||||
|
||||
msgid "Select Group"
|
||||
msgstr "选择分组"
|
||||
|
||||
msgid "Node Matching Rules"
|
||||
msgstr "节点匹配规则"
|
||||
|
||||
msgid ""
|
||||
"Example: <code>^A && B && !C && D$</code><br>"
|
||||
"This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
|
||||
"Conditions are joined by <code>&&</code>, and their order does not affect the result."
|
||||
msgstr ""
|
||||
"示例:<code>^A && B && !C && D$</code><br>"
|
||||
"表示节点备注需同时满足:以 A 开头(^)、包含 B、不包含 C(!)、并以 D 结尾($)。<br>"
|
||||
"多个条件使用 <code>&&</code> 连接,条件顺序不影响结果。"
|
||||
|
||||
msgid "Balancing Strategy"
|
||||
msgstr "负载均衡策略"
|
||||
|
||||
@@ -412,8 +436,11 @@ msgstr "前置代理"
|
||||
msgid "Preproxy Node"
|
||||
msgstr "前置代理节点"
|
||||
|
||||
msgid "Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not."
|
||||
msgstr "设置用作前置代理的节点。每条规则(包括<code>默认</code>)都有独立开关控制本规则是否使用前置代理。"
|
||||
msgid "Set the node to be used as a pre-proxy."
|
||||
msgstr "设置用作前置代理的节点。"
|
||||
|
||||
msgid "Each rule has a separate switch that controls whether this rule uses the pre-proxy or not."
|
||||
msgstr "每条规则都有独立开关控制本规则是否使用前置代理。"
|
||||
|
||||
msgid "Close (Not use)"
|
||||
msgstr "关闭 (不使用)"
|
||||
@@ -427,12 +454,6 @@ msgstr "直连"
|
||||
msgid "Blackhole (Block)"
|
||||
msgstr "黑洞(屏蔽)"
|
||||
|
||||
msgid "Use preproxy node"
|
||||
msgstr "使用前置代理节点"
|
||||
|
||||
msgid "Default Preproxy"
|
||||
msgstr "默认前置代理"
|
||||
|
||||
msgid "There are no available nodes, please add or subscribe nodes first."
|
||||
msgstr "没有可用节点,请先添加或订阅节点。"
|
||||
|
||||
@@ -748,18 +769,6 @@ msgstr "恢复切换"
|
||||
msgid "When detects main node is available, switch back to the main node."
|
||||
msgstr "当检测到主节点可用时,切换回主节点。"
|
||||
|
||||
msgid "If the main node is shunt"
|
||||
msgstr "如果主节点是分流"
|
||||
|
||||
msgid "Switch it"
|
||||
msgstr "切掉它"
|
||||
|
||||
msgid "Applying to the default node"
|
||||
msgstr "应用于默认节点"
|
||||
|
||||
msgid "Applying to the default preproxy node"
|
||||
msgstr "应用于默认前置节点"
|
||||
|
||||
msgid "Add nodes to the standby node list by keywords"
|
||||
msgstr "通过关键字添加节点到备用节点列表"
|
||||
|
||||
|
||||
@@ -367,6 +367,30 @@ msgstr "分流"
|
||||
msgid "Balancing"
|
||||
msgstr "負載均衡"
|
||||
|
||||
msgid "Node Addition Method"
|
||||
msgstr "節點新增方式"
|
||||
|
||||
msgid "Manual"
|
||||
msgstr "手動"
|
||||
|
||||
msgid "Batch"
|
||||
msgstr "批量"
|
||||
|
||||
msgid "Select Group"
|
||||
msgstr "選擇分組"
|
||||
|
||||
msgid "Node Matching Rules"
|
||||
msgstr "節點匹配規則"
|
||||
|
||||
msgid ""
|
||||
"Example: <code>^A && B && !C && D$</code><br>"
|
||||
"This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
|
||||
"Conditions are joined by <code>&&</code>, and their order does not affect the result."
|
||||
msgstr ""
|
||||
"示例:<code>^A && B && !C && D$</code><br>"
|
||||
"表示節點備註需同時滿足:以 A 開頭(^)、包含 B、不包含 C(!)、並以 D 結尾($)。<br>"
|
||||
"多個條件使用 <code>&&</code> 連接,條件順序不影響結果。"
|
||||
|
||||
msgid "Balancing Strategy"
|
||||
msgstr "負載均衡策略"
|
||||
|
||||
@@ -412,8 +436,11 @@ msgstr "前置代理"
|
||||
msgid "Preproxy Node"
|
||||
msgstr "前置代理節點"
|
||||
|
||||
msgid "Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not."
|
||||
msgstr "設置用作前置代理的節點。每条規則(包括<code>默認</code>)都有独立開關控制本規則是否使用前置代理。"
|
||||
msgid "Set the node to be used as a pre-proxy."
|
||||
msgstr "設置用作前置代理的節點。"
|
||||
|
||||
msgid "Each rule has a separate switch that controls whether this rule uses the pre-proxy or not."
|
||||
msgstr "每条規則都有独立開關控制本規則是否使用前置代理。"
|
||||
|
||||
msgid "Close (Not use)"
|
||||
msgstr "關閉 (不使用)"
|
||||
@@ -427,12 +454,6 @@ msgstr "直連"
|
||||
msgid "Blackhole (Block)"
|
||||
msgstr "黑洞(屏蔽)"
|
||||
|
||||
msgid "Use preproxy node"
|
||||
msgstr "使用前置代理節點"
|
||||
|
||||
msgid "Default Preproxy"
|
||||
msgstr "默認前置代理"
|
||||
|
||||
msgid "There are no available nodes, please add or subscribe nodes first."
|
||||
msgstr "沒有可用節點,請先添加或訂閱節點。"
|
||||
|
||||
@@ -748,18 +769,6 @@ msgstr "恢復切換"
|
||||
msgid "When detects main node is available, switch back to the main node."
|
||||
msgstr "當檢測到主節點可用時,切換回主節點。"
|
||||
|
||||
msgid "If the main node is shunt"
|
||||
msgstr "如果主節點是分流"
|
||||
|
||||
msgid "Switch it"
|
||||
msgstr "切掉它"
|
||||
|
||||
msgid "Applying to the default node"
|
||||
msgstr "應用於默認節點"
|
||||
|
||||
msgid "Applying to the default preproxy node"
|
||||
msgstr "應用於默認前置節點"
|
||||
|
||||
msgid "Add nodes to the standby node list by keywords"
|
||||
msgstr "通過關键字添加節點到備用節點列表"
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ local ssub, slen, schar, sbyte, sformat, sgsub = string.sub, string.len, string.
|
||||
local split = api.split
|
||||
local jsonParse, jsonStringify = luci.jsonc.parse, luci.jsonc.stringify
|
||||
local base64Decode = api.base64Decode
|
||||
local UrlEncode = api.UrlEncode
|
||||
local UrlDecode = api.UrlDecode
|
||||
local uci = api.uci
|
||||
local fs = api.fs
|
||||
local log = api.log
|
||||
@@ -247,10 +249,6 @@ do
|
||||
[".name"] = "default_node",
|
||||
remarks = i18n.translatef("Default")
|
||||
})
|
||||
table.insert(rules, {
|
||||
[".name"] = "main_node",
|
||||
remarks = i18n.translatef("Default Preproxy")
|
||||
})
|
||||
|
||||
for k, e in pairs(rules) do
|
||||
local _node_id = node[e[".name"]] or nil
|
||||
@@ -403,18 +401,6 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
local function UrlEncode(szText)
|
||||
return szText:gsub("([^%w%-_%.%~])", function(c)
|
||||
return string.format("%%%02X", string.byte(c))
|
||||
end)
|
||||
end
|
||||
|
||||
local function UrlDecode(szText)
|
||||
return szText and szText:gsub("+", " "):gsub("%%(%x%x)", function(h)
|
||||
return string.char(tonumber(h, 16))
|
||||
end) or nil
|
||||
end
|
||||
|
||||
-- Retrieve subscribe information (remaining data allowance, expiration time).
|
||||
local subscribe_info = {}
|
||||
local function get_subscribe_info(cfgid, value)
|
||||
@@ -617,6 +603,8 @@ local function processData(szType, content, add_mode, group)
|
||||
result.tls = "0"
|
||||
end
|
||||
|
||||
result.tcp_fast_open = info.tfo
|
||||
|
||||
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
|
||||
log(2, i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
|
||||
return nil
|
||||
@@ -719,13 +707,17 @@ local function processData(szType, content, add_mode, group)
|
||||
|
||||
result.method = method
|
||||
result.password = password
|
||||
result.tcp_fast_open = params.tfo
|
||||
|
||||
if has_xray and (result.type ~= 'Xray' and result.type ~= 'sing-box' and params.type) then
|
||||
result.type = 'Xray'
|
||||
result.protocol = 'shadowsocks'
|
||||
elseif has_singbox and (result.type ~= 'Xray' and result.type ~= 'sing-box' and params.type) then
|
||||
result.type = 'sing-box'
|
||||
result.protocol = 'shadowsocks'
|
||||
local need_upgrade = (result.type ~= "Xray" and result.type ~= "sing-box")
|
||||
and (params.type and params.type ~= "tcp")
|
||||
and (params.headerType and params.headerType ~= "none")
|
||||
if has_xray and (need_upgrade or params.type == "xhttp") then
|
||||
result.type = "Xray"
|
||||
result.protocol = "shadowsocks"
|
||||
elseif has_singbox and need_upgrade then
|
||||
result.type = "sing-box"
|
||||
result.protocol = "shadowsocks"
|
||||
end
|
||||
|
||||
if result.plugin then
|
||||
@@ -888,7 +880,8 @@ local function processData(szType, content, add_mode, group)
|
||||
else
|
||||
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
|
||||
end
|
||||
else
|
||||
result.uot = params.udp
|
||||
elseif (params.type ~= "tcp" and params.type ~= "raw") and (params.headerType and params.headerType ~= "none") then
|
||||
result.error_msg = i18n.translatef("Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks.")
|
||||
end
|
||||
end
|
||||
@@ -1090,6 +1083,7 @@ local function processData(szType, content, add_mode, group)
|
||||
end
|
||||
|
||||
result.alpn = params.alpn
|
||||
result.tcp_fast_open = params.tfo
|
||||
|
||||
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
|
||||
log(2, i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
|
||||
@@ -1278,6 +1272,8 @@ local function processData(szType, content, add_mode, group)
|
||||
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
|
||||
end
|
||||
|
||||
result.tcp_fast_open = params.tfo
|
||||
|
||||
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
|
||||
log(2, i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
shadowsocks-libev is a lightweight SOCKS5 proxy written in pure C. Version 3.3.6, licensed under GPLv3.
|
||||
|
||||
## Build Commands
|
||||
|
||||
### CMake (sole build system)
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
mkdir -p build && cd build
|
||||
cmake ..
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
On macOS, CMake should auto-detect library paths. If needed, specify paths:
|
||||
```bash
|
||||
cmake .. -DCMAKE_PREFIX_PATH="/usr/local/opt/mbedtls;/usr/local/opt/libsodium"
|
||||
```
|
||||
|
||||
CMake outputs binaries to `build/bin/` (static) and `build/shared/bin/` (shared).
|
||||
|
||||
### Build Dependencies
|
||||
|
||||
- cmake (>= 3.2), a C compiler (gcc or clang), pkg-config
|
||||
- libmbedtls, libsodium (>= 1.0.4), libpcre3, libev, libc-ares
|
||||
- asciidoc + xmlto (documentation only)
|
||||
|
||||
### CMake Options
|
||||
|
||||
- `-DWITH_EMBEDDED_SRC=OFF`: use system libcork/libipset/libbloom instead of bundled submodules
|
||||
- `-DWITH_DOC_MAN=OFF`: skip man page generation
|
||||
- `-DENABLE_CONNMARKTOS=ON`: Linux netfilter conntrack QoS support
|
||||
- `-DENABLE_NFTABLES=ON`: nftables firewall integration
|
||||
- `-DDISABLE_SSP=ON`: disable stack protector
|
||||
- `-DBUILD_TESTING=OFF`: disable unit tests
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Tests (CTest)
|
||||
|
||||
```bash
|
||||
cd build
|
||||
ctest --output-on-failure
|
||||
```
|
||||
|
||||
10 unit test modules cover: base64, buffer, crypto, json, jconf, cache, ppbloom, rule, netutils, utils.
|
||||
|
||||
### Integration Tests
|
||||
|
||||
Integration tests use Python and require `curl` and `dig` to be available:
|
||||
```bash
|
||||
bash tests/test.sh
|
||||
```
|
||||
|
||||
The test harness (`tests/test.py`) starts ss-server, ss-local, and ss-tunnel locally, then runs curl through the SOCKS5 proxy and dig through the tunnel. Each test config in `tests/*.json` exercises a different cipher.
|
||||
|
||||
Run a single cipher test:
|
||||
```bash
|
||||
python tests/test.py --bin build/bin/ -c tests/aes-gcm.json
|
||||
```
|
||||
|
||||
## Code Formatting
|
||||
|
||||
Uses **uncrustify** with the config at `.uncrustify.cfg`. Key settings: 4-space indent, no tabs, 120-column width, K&R brace style (braces on same line).
|
||||
|
||||
## Architecture
|
||||
|
||||
### Binaries (all in `src/`)
|
||||
|
||||
Each binary is compiled with a module define that controls conditional compilation:
|
||||
|
||||
| Binary | Define | Purpose |
|
||||
|---|---|---|
|
||||
| `ss-local` | `MODULE_LOCAL` | SOCKS5 client proxy |
|
||||
| `ss-server` | `MODULE_REMOTE` | Server-side proxy |
|
||||
| `ss-tunnel` | `MODULE_TUNNEL` | Port forwarding tunnel (implies `MODULE_LOCAL`) |
|
||||
| `ss-redir` | `MODULE_REDIR` | Transparent proxy via iptables (Linux only, implies `MODULE_LOCAL`) |
|
||||
| `ss-manager` | `MODULE_MANAGER` | Multi-server manager daemon |
|
||||
|
||||
A shared library `libshadowsocks-libev` is also built from the ss-local sources with `-DLIB_ONLY`. Its public API is in `src/shadowsocks.h`.
|
||||
|
||||
### Source Organization (`src/`)
|
||||
|
||||
**Shared by all binaries:**
|
||||
- `utils.c` - logging, system utilities
|
||||
- `jconf.c` / `json.c` - JSON config file parsing
|
||||
- `netutils.c` - network address utilities
|
||||
- `cache.c` - hash-based LRU connection cache
|
||||
- `udprelay.c` - UDP relay implementation (shared, but uses `#ifdef MODULE_*` for per-binary behavior)
|
||||
|
||||
**Crypto layer** (two parallel implementations behind a common `crypto_t` interface):
|
||||
- `crypto.c` / `crypto.h` - crypto initialization, key derivation (HKDF), buffer management. Defines `crypto_t` with function pointers for encrypt/decrypt.
|
||||
- `stream.c` - stream cipher implementation (CFB mode via mbedTLS)
|
||||
- `aead.c` - AEAD cipher implementation (AES-GCM via mbedTLS, ChaCha20-Poly1305 via libsodium)
|
||||
- `ppbloom.c` - ping-pong bloom filter for nonce replay detection
|
||||
|
||||
**ACL (Access Control Lists):**
|
||||
- `acl.c` / `rule.c` - IP/domain-based routing rules using libipset
|
||||
|
||||
**Plugin support:**
|
||||
- `plugin.c` - SIP003 plugin subprocess management
|
||||
|
||||
### Bundled Submodules
|
||||
|
||||
Three git submodules in the repo root (can be replaced with system libs via `-DWITH_EMBEDDED_SRC=OFF`):
|
||||
- `libcork/` - data structures (dllist, hash-table, buffers)
|
||||
- `libipset/` - IP set operations for ACL
|
||||
- `libbloom/` - bloom filter implementation
|
||||
|
||||
### Event Loop
|
||||
|
||||
All binaries use **libev** for async I/O. The connection lifecycle follows stages defined in `src/common.h`: `STAGE_INIT` -> `STAGE_HANDSHAKE` -> `STAGE_RESOLVE` -> `STAGE_STREAM` -> `STAGE_STOP`. Each binary defines its own `listen_ctx_t`, `server_t`, and `remote_t` structs (note: "server" in `local.h` means the local-side connection, "remote" means the ss-server side).
|
||||
|
||||
### Compiler Flags
|
||||
|
||||
Default flags from `CMakeLists.txt`: `-g -O2 -Wall -Werror -Wno-deprecated-declarations -fno-strict-aliasing -std=gnu99 -D_GNU_SOURCE`
|
||||
|
||||
The `-Werror` flag means all warnings are errors - new code must compile warning-free.
|
||||
@@ -1,8 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(PROJECT_NAME shadowsocks-libev)
|
||||
set(RELEASE_DATE 2020-09-15)
|
||||
set(PROJECT_VERSION "3.3.5")
|
||||
set(RELEASE_DATE 2026-02-09)
|
||||
set(PROJECT_VERSION "3.3.6")
|
||||
set(PROJECT_DESC "a lightweight secured socks5 proxy")
|
||||
set(PROJECT_URL "https://shadowsocks.org")
|
||||
set(PROJECT_ISSUES_URL "https://github.com/shadowsocks/shadowsocks-libev")
|
||||
|
||||
@@ -11,7 +11,7 @@ It is a port of [Shadowsocks](https://github.com/shadowsocks/shadowsocks)
|
||||
created by [@clowwindy](https://github.com/clowwindy), and maintained by
|
||||
[@madeye](https://github.com/madeye) and [@linusyang](https://github.com/linusyang).
|
||||
|
||||
Current version: 3.3.5 | [Changelog](debian/changelog)
|
||||
Current version: 3.3.6 | [Changelog](debian/changelog)
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ proxy para dispositivos embutidos e caixas de baixo custo.
|
||||
criado por [@clowwindy](https://github.com/clowwindy) e mantido por
|
||||
[@madeye](https://github.com/madeye) e [@linusyang](https://github.com/linusyang).
|
||||
|
||||
Versão atual: 3.3.5 | [Changelog](debian/changelog)
|
||||
Versão atual: 3.3.6 | [Changelog](debian/changelog)
|
||||
|
||||
## Características
|
||||
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
shadowsocks-libev (3.3.6-1) unstable; urgency=medium
|
||||
|
||||
* Reduce memory copies in crypto encrypt/decrypt paths.
|
||||
* Fix unbounded buffer growth in aead_decrypt chunk reassembly.
|
||||
|
||||
-- Max Lv <max.c.lv@gmail.com> Sun, 09 Feb 2026 08:00:00 +0800
|
||||
|
||||
shadowsocks-libev (3.3.5-1) unstable; urgency=medium
|
||||
|
||||
* Remove the SNI proxy function.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
base: core18
|
||||
name: shadowsocks-libev
|
||||
version: 3.3.5-1
|
||||
version: 3.3.6-1
|
||||
summary: libev port of shadowsocks
|
||||
description: |
|
||||
Shadowsocks-libev is a lightweight and secure SOCKS5 proxy for embedded
|
||||
|
||||
@@ -439,8 +439,7 @@ aead_encrypt_all(buffer_t *plaintext, cipher_t *cipher, size_t capacity)
|
||||
|
||||
assert(ciphertext->len == clen);
|
||||
|
||||
brealloc(plaintext, salt_len + ciphertext->len, capacity);
|
||||
memcpy(plaintext->data, ciphertext->data, salt_len + ciphertext->len);
|
||||
bswap_data(plaintext, ciphertext);
|
||||
plaintext->len = salt_len + ciphertext->len;
|
||||
|
||||
return CRYPTO_OK;
|
||||
@@ -490,8 +489,7 @@ aead_decrypt_all(buffer_t *ciphertext, cipher_t *cipher, size_t capacity)
|
||||
|
||||
ppbloom_add((void *)salt, salt_len);
|
||||
|
||||
brealloc(ciphertext, plaintext->len, capacity);
|
||||
memcpy(ciphertext->data, plaintext->data, plaintext->len);
|
||||
bswap_data(ciphertext, plaintext);
|
||||
ciphertext->len = plaintext->len;
|
||||
|
||||
return CRYPTO_OK;
|
||||
@@ -579,8 +577,7 @@ aead_encrypt(buffer_t *plaintext, cipher_ctx_t *cipher_ctx, size_t capacity)
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
brealloc(plaintext, ciphertext->len, capacity);
|
||||
memcpy(plaintext->data, ciphertext->data, ciphertext->len);
|
||||
bswap_data(plaintext, ciphertext);
|
||||
plaintext->len = ciphertext->len;
|
||||
|
||||
return 0;
|
||||
@@ -634,9 +631,7 @@ aead_chunk_decrypt(cipher_ctx_t *ctx, uint8_t *p, uint8_t *c, uint8_t *n,
|
||||
int
|
||||
aead_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
|
||||
{
|
||||
int err = CRYPTO_OK;
|
||||
static buffer_t tmp = { 0, 0, 0, NULL };
|
||||
|
||||
int err = CRYPTO_OK;
|
||||
cipher_t *cipher = cipher_ctx->cipher;
|
||||
|
||||
size_t salt_len = cipher->key_len;
|
||||
@@ -647,20 +642,32 @@ aead_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
|
||||
balloc(cipher_ctx->chunk, capacity);
|
||||
}
|
||||
|
||||
brealloc(cipher_ctx->chunk,
|
||||
cipher_ctx->chunk->len + ciphertext->len, capacity);
|
||||
memcpy(cipher_ctx->chunk->data + cipher_ctx->chunk->len,
|
||||
ciphertext->data, ciphertext->len);
|
||||
cipher_ctx->chunk->len += ciphertext->len;
|
||||
buffer_t *chunk = cipher_ctx->chunk;
|
||||
|
||||
brealloc(&tmp, cipher_ctx->chunk->len, capacity);
|
||||
buffer_t *plaintext = &tmp;
|
||||
if (chunk->len == 0) {
|
||||
bswap_data(chunk, ciphertext);
|
||||
chunk->len = ciphertext->len;
|
||||
chunk->idx = 0;
|
||||
ciphertext->len = 0;
|
||||
} else {
|
||||
if (chunk->idx > 0) {
|
||||
memmove(chunk->data, chunk->data + chunk->idx, chunk->len);
|
||||
chunk->idx = 0;
|
||||
}
|
||||
brealloc(chunk, chunk->len + ciphertext->len, capacity);
|
||||
memcpy(chunk->data + chunk->len,
|
||||
ciphertext->data, ciphertext->len);
|
||||
chunk->len += ciphertext->len;
|
||||
}
|
||||
|
||||
// Write plaintext directly into the (now-free) ciphertext buffer.
|
||||
brealloc(ciphertext, chunk->len, capacity);
|
||||
|
||||
if (!cipher_ctx->init) {
|
||||
if (cipher_ctx->chunk->len <= salt_len)
|
||||
if (chunk->len <= salt_len)
|
||||
return CRYPTO_NEED_MORE;
|
||||
|
||||
memcpy(cipher_ctx->salt, cipher_ctx->chunk->data, salt_len);
|
||||
memcpy(cipher_ctx->salt, chunk->data + chunk->idx, salt_len);
|
||||
|
||||
if (ppbloom_check((void *)cipher_ctx->salt, salt_len) == 1) {
|
||||
LOGE("crypto: AEAD: repeat salt detected");
|
||||
@@ -669,38 +676,40 @@ aead_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
|
||||
|
||||
aead_cipher_ctx_set_key(cipher_ctx, 0);
|
||||
|
||||
memmove(cipher_ctx->chunk->data, cipher_ctx->chunk->data + salt_len,
|
||||
cipher_ctx->chunk->len - salt_len);
|
||||
cipher_ctx->chunk->len -= salt_len;
|
||||
chunk->idx += salt_len;
|
||||
chunk->len -= salt_len;
|
||||
|
||||
cipher_ctx->init = 1;
|
||||
}
|
||||
|
||||
size_t plen = 0;
|
||||
size_t cidx = 0;
|
||||
while (cipher_ctx->chunk->len > 0) {
|
||||
size_t chunk_clen = cipher_ctx->chunk->len;
|
||||
while (chunk->len > 0) {
|
||||
size_t chunk_clen = chunk->len;
|
||||
size_t chunk_plen = 0;
|
||||
err = aead_chunk_decrypt(cipher_ctx,
|
||||
(uint8_t *)plaintext->data + plen,
|
||||
(uint8_t *)cipher_ctx->chunk->data + cidx,
|
||||
(uint8_t *)ciphertext->data + plen,
|
||||
(uint8_t *)chunk->data + chunk->idx + cidx,
|
||||
cipher_ctx->nonce, &chunk_plen, &chunk_clen);
|
||||
if (err == CRYPTO_ERROR) {
|
||||
return err;
|
||||
} else if (err == CRYPTO_NEED_MORE) {
|
||||
if (plen == 0)
|
||||
return err;
|
||||
else{
|
||||
memmove((uint8_t *)cipher_ctx->chunk->data,
|
||||
(uint8_t *)cipher_ctx->chunk->data + cidx, chunk_clen);
|
||||
else {
|
||||
chunk->idx += cidx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cipher_ctx->chunk->len = chunk_clen;
|
||||
chunk->len = chunk_clen;
|
||||
cidx += cipher_ctx->cipher->tag_len * 2 + CHUNK_SIZE_LEN + chunk_plen;
|
||||
plen += chunk_plen;
|
||||
plen += chunk_plen;
|
||||
}
|
||||
plaintext->len = plen;
|
||||
|
||||
if (chunk->len == 0)
|
||||
chunk->idx = 0;
|
||||
|
||||
ciphertext->len = plen;
|
||||
|
||||
// Add the salt to bloom filter
|
||||
if (cipher_ctx->init == 1) {
|
||||
@@ -712,10 +721,6 @@ aead_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
|
||||
cipher_ctx->init = 2;
|
||||
}
|
||||
|
||||
brealloc(ciphertext, plaintext->len, capacity);
|
||||
memcpy(ciphertext->data, plaintext->data, plaintext->len);
|
||||
ciphertext->len = plaintext->len;
|
||||
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,18 @@ int balloc(buffer_t *, size_t);
|
||||
int brealloc(buffer_t *, size_t, size_t);
|
||||
int bprepend(buffer_t *, buffer_t *, size_t);
|
||||
void bfree(buffer_t *);
|
||||
|
||||
static inline void
|
||||
bswap_data(buffer_t *a, buffer_t *b)
|
||||
{
|
||||
char *tmp_data = a->data;
|
||||
size_t tmp_capacity = a->capacity;
|
||||
a->data = b->data;
|
||||
a->capacity = b->capacity;
|
||||
b->data = tmp_data;
|
||||
b->capacity = tmp_capacity;
|
||||
}
|
||||
|
||||
int rand_bytes(void *, int);
|
||||
|
||||
crypto_t *crypto_init(const char *, const char *, const char *);
|
||||
|
||||
@@ -344,8 +344,7 @@ stream_encrypt_all(buffer_t *plaintext, cipher_t *cipher, size_t capacity)
|
||||
dump("NONCE", ciphertext->data, nonce_len);
|
||||
#endif
|
||||
|
||||
brealloc(plaintext, nonce_len + ciphertext->len, capacity);
|
||||
memcpy(plaintext->data, ciphertext->data, nonce_len + ciphertext->len);
|
||||
bswap_data(plaintext, ciphertext);
|
||||
plaintext->len = nonce_len + ciphertext->len;
|
||||
|
||||
return CRYPTO_OK;
|
||||
@@ -359,6 +358,19 @@ stream_encrypt(buffer_t *plaintext, cipher_ctx_t *cipher_ctx, size_t capacity)
|
||||
|
||||
cipher_t *cipher = cipher_ctx->cipher;
|
||||
|
||||
// In-place fast path for non-Salsa20 ciphers after init.
|
||||
// mbedtls_cipher_update supports output == input for CFB/CTR stream modes.
|
||||
if (cipher_ctx->init && cipher->method < SALSA20) {
|
||||
size_t out_len = plaintext->len;
|
||||
int err = cipher_ctx_update(cipher_ctx,
|
||||
(uint8_t *)plaintext->data, &out_len,
|
||||
(const uint8_t *)plaintext->data, plaintext->len);
|
||||
if (err)
|
||||
return CRYPTO_ERROR;
|
||||
plaintext->len = out_len;
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
|
||||
static buffer_t tmp = { 0, 0, 0, NULL };
|
||||
|
||||
int err = CRYPTO_OK;
|
||||
@@ -416,8 +428,7 @@ stream_encrypt(buffer_t *plaintext, cipher_ctx_t *cipher_ctx, size_t capacity)
|
||||
dump("CIPHER", ciphertext->data + nonce_len, ciphertext->len);
|
||||
#endif
|
||||
|
||||
brealloc(plaintext, nonce_len + ciphertext->len, capacity);
|
||||
memcpy(plaintext->data, ciphertext->data, nonce_len + ciphertext->len);
|
||||
bswap_data(plaintext, ciphertext);
|
||||
plaintext->len = nonce_len + ciphertext->len;
|
||||
|
||||
return CRYPTO_OK;
|
||||
@@ -475,8 +486,7 @@ stream_decrypt_all(buffer_t *ciphertext, cipher_t *cipher, size_t capacity)
|
||||
|
||||
ppbloom_add((void *)nonce, nonce_len);
|
||||
|
||||
brealloc(ciphertext, plaintext->len, capacity);
|
||||
memcpy(ciphertext->data, plaintext->data, plaintext->len);
|
||||
bswap_data(ciphertext, plaintext);
|
||||
ciphertext->len = plaintext->len;
|
||||
|
||||
return CRYPTO_OK;
|
||||
@@ -490,6 +500,29 @@ stream_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
|
||||
|
||||
cipher_t *cipher = cipher_ctx->cipher;
|
||||
|
||||
// In-place fast path for non-Salsa20 ciphers after init.
|
||||
// mbedtls_cipher_update supports output == input for CFB/CTR stream modes.
|
||||
if (cipher_ctx->init && cipher->method < SALSA20) {
|
||||
if (ciphertext->len <= 0)
|
||||
return CRYPTO_NEED_MORE;
|
||||
size_t out_len = ciphertext->len;
|
||||
int err = cipher_ctx_update(cipher_ctx,
|
||||
(uint8_t *)ciphertext->data, &out_len,
|
||||
(const uint8_t *)ciphertext->data, ciphertext->len);
|
||||
if (err)
|
||||
return CRYPTO_ERROR;
|
||||
ciphertext->len = out_len;
|
||||
if (cipher_ctx->init == 1 && cipher->method >= RC4_MD5) {
|
||||
if (ppbloom_check((void *)cipher_ctx->nonce, cipher->nonce_len) == 1) {
|
||||
LOGE("crypto: stream: repeat IV detected");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
ppbloom_add((void *)cipher_ctx->nonce, cipher->nonce_len);
|
||||
cipher_ctx->init = 2;
|
||||
}
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
|
||||
static buffer_t tmp = { 0, 0, 0, NULL };
|
||||
|
||||
int err = CRYPTO_OK;
|
||||
@@ -585,8 +618,7 @@ stream_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
|
||||
}
|
||||
}
|
||||
|
||||
brealloc(ciphertext, plaintext->len, capacity);
|
||||
memcpy(ciphertext->data, plaintext->data, plaintext->len);
|
||||
bswap_data(ciphertext, plaintext);
|
||||
ciphertext->len = plaintext->len;
|
||||
|
||||
return CRYPTO_OK;
|
||||
|
||||
@@ -1026,17 +1026,17 @@ These Ciphers require `"password"` to be a Base64 string of key that have **exac
|
||||
|
||||
- For local servers (`sslocal`, `ssredir`, ...)
|
||||
- Modes:
|
||||
- `[bypass_all]` - ACL runs in `WhiteList` mode. Bypasses all addresses except those matched any rules.
|
||||
- `[proxy_all]` - ACL runs in `BlackList` mode. Proxies all addresses except those matched any rules. (default)
|
||||
- `[bypass_all]` - ACL runs in `WhiteList` mode. Bypasses all addresses except those matching any rules.
|
||||
- `[proxy_all]` - ACL runs in `BlackList` mode. Proxies all addresses except those matching any rules. (default)
|
||||
- Rules:
|
||||
- `[bypass_list]` - Rules for connecting directly
|
||||
- `[proxy_list]` - Rules for connecting through proxies
|
||||
- For remote servers (`ssserver`)
|
||||
- Modes:
|
||||
- `[reject_all]` - ACL runs in `WhiteList` mode. Rejects all clients except those matched any rules.
|
||||
- `[accept_all]` - ACL runs in `BlackList` mode. Accepts all clients except those matched any rules. (default)
|
||||
- `[outbound_block_all]` - Outbound ACL runs in `WhiteList` mode. Blockes all outbound addresses except those matched any rules.
|
||||
- `[outbound_allow_all]` - Outbound ACL runs in `BlackList` mode. Allows all outbound addresses except those matched any rules. (default)
|
||||
- `[reject_all]` - ACL runs in `WhiteList` mode. Rejects all clients except those matching any rules.
|
||||
- `[accept_all]` - ACL runs in `BlackList` mode. Accepts all clients except those matching any rules. (default)
|
||||
- `[outbound_block_all]` - Outbound ACL runs in `WhiteList` mode. Blocks all outbound addresses except those matching any rules.
|
||||
- `[outbound_allow_all]` - Outbound ACL runs in `BlackList` mode. Allows all outbound addresses except those matching any rules. (default)
|
||||
- Rules:
|
||||
- `[white_list]` - Rules for accepted clients
|
||||
- `[black_list]` - Rules for rejected clients
|
||||
|
||||
@@ -21,7 +21,7 @@ use windows_service::{
|
||||
|
||||
const SERVICE_NAME: &str = "ssservice";
|
||||
const SERVICE_EXIT_CODE_ARGUMENT_ERROR: u32 = 100;
|
||||
const SERVICE_EXIT_CODE_EXITED_UNEXPECTLY: u32 = 101;
|
||||
const SERVICE_EXIT_CODE_EXITED_UNEXPECTEDLY: u32 = 101;
|
||||
const SERVICE_EXIT_CODE_CREATE_FAILED: u32 = 102;
|
||||
|
||||
#[inline]
|
||||
@@ -84,7 +84,7 @@ where
|
||||
break true;
|
||||
}
|
||||
exit_code = main_fut => {
|
||||
info!("service exited unexpectly with code: {:?}", exit_code);
|
||||
info!("service exited unexpectedly with code: {:?}", exit_code);
|
||||
break false;
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ where
|
||||
if exited_by_ctrl {
|
||||
ServiceExitCode::Win32(0)
|
||||
} else {
|
||||
ServiceExitCode::ServiceSpecific(SERVICE_EXIT_CODE_EXITED_UNEXPECTLY)
|
||||
ServiceExitCode::ServiceSpecific(SERVICE_EXIT_CODE_EXITED_UNEXPECTEDLY)
|
||||
},
|
||||
Duration::default(),
|
||||
)?;
|
||||
|
||||
@@ -619,8 +619,8 @@ impl AccessControl {
|
||||
// If no domain name rules matched,
|
||||
// we need to resolve the hostname to IP addresses
|
||||
|
||||
// If mode is BlackList, host is allowed by default. If any of its' resolved IPs in outboud_block, then it is blocked.
|
||||
// If mode is WhiteList, host is blocked by default. If any of its' resolved IPs in outbound_allow, then it is allowed.
|
||||
// If mode is BlackList, host is allowed by default. If any of its resolved IPs is in outbound_block, then it is blocked.
|
||||
// If mode is WhiteList, host is blocked by default. If any of its resolved IPs is in outbound_allow, then it is allowed.
|
||||
let (check_rule, block_if_matched) = match self.outbound_mode {
|
||||
Mode::BlackList => (&self.outbound_block, true),
|
||||
Mode::WhiteList => (&self.outbound_allow, false),
|
||||
|
||||
@@ -883,7 +883,7 @@ impl DnsClient {
|
||||
.map_err(From::from)
|
||||
}
|
||||
Mode::TcpAndUdp => {
|
||||
// Query TCP & UDP simutaneously
|
||||
// Query TCP & UDP simultaneously
|
||||
|
||||
let message2 = message.clone();
|
||||
let tcp_fut = async {
|
||||
@@ -952,7 +952,7 @@ impl DnsClient {
|
||||
self.client_cache
|
||||
.lookup_local(ns, message.clone(), self.context.connect_opts_ref(), true);
|
||||
let tcp_query = async move {
|
||||
// Send TCP query after 500ms, because UDP will always return faster than TCP, there is no need to send queries simutaneously
|
||||
// Send TCP query after 500ms, because UDP will always return faster than TCP, there is no need to send queries simultaneously
|
||||
time::sleep(Duration::from_millis(500)).await;
|
||||
|
||||
self.client_cache
|
||||
|
||||
@@ -77,7 +77,7 @@ impl Debug for ServerScore {
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifer for a server
|
||||
/// Identifier for a server
|
||||
#[derive(Debug)]
|
||||
pub struct ServerIdent {
|
||||
tcp_score: ServerScore,
|
||||
|
||||
@@ -90,7 +90,7 @@ impl ServerStat {
|
||||
|
||||
// Normalize stdev
|
||||
// let nstdev = self.data.latency_stdev / self.max_latency_stdev;
|
||||
// Mormalize mad
|
||||
// Normalize mad
|
||||
let nmad = self.data.latency_mad as f64 / self.max_server_rtt as f64;
|
||||
|
||||
const SCORE_RTT_WEIGHT: f64 = 1.0;
|
||||
|
||||
@@ -128,7 +128,7 @@ impl OnlineConfigService {
|
||||
}
|
||||
};
|
||||
|
||||
trace!("sever-loader task fetch response: {:?}", rsp);
|
||||
trace!("server-loader task fetch response: {:?}", rsp);
|
||||
|
||||
let fetch_time = Instant::now();
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ impl RedirUdpServer {
|
||||
}
|
||||
|
||||
peer_addr_opt = keepalive_rx.recv() => {
|
||||
let peer_addr = peer_addr_opt.expect("keep-alive channel closed unexpectly");
|
||||
let peer_addr = peer_addr_opt.expect("keep-alive channel closed unexpectedly");
|
||||
manager.keep_alive(&peer_addr).await;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -167,7 +167,7 @@ impl Socks5UdpServer {
|
||||
}
|
||||
|
||||
peer_addr_opt = keepalive_rx.recv() => {
|
||||
let peer_addr = peer_addr_opt.expect("keep-alive channel closed unexpectly");
|
||||
let peer_addr = peer_addr_opt.expect("keep-alive channel closed unexpectedly");
|
||||
manager.keep_alive(&peer_addr).await;
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ async fn handle_tcp_client(
|
||||
let server = balancer.best_tcp_server();
|
||||
let svr_cfg = server.server_config();
|
||||
trace!(
|
||||
"establishing tcp tunnel {} <-> {} through sever {} (outbound: {})",
|
||||
"establishing tcp tunnel {} <-> {} through server {} (outbound: {})",
|
||||
peer_addr,
|
||||
forward_addr,
|
||||
svr_cfg.tcp_external_addr(),
|
||||
|
||||
@@ -134,7 +134,7 @@ impl TunnelUdpServer {
|
||||
}
|
||||
|
||||
peer_addr_opt = keepalive_rx.recv() => {
|
||||
let peer_addr = peer_addr_opt.expect("keep-alive channel closed unexpectly");
|
||||
let peer_addr = peer_addr_opt.expect("keep-alive channel closed unexpectedly");
|
||||
manager.keep_alive(&peer_addr).await;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ where
|
||||
{
|
||||
if shadow.is_proxied() {
|
||||
debug!(
|
||||
"established tcp tunnel {} <-> {} through sever {} (outbound: {})",
|
||||
"established tcp tunnel {} <-> {} through server {} (outbound: {})",
|
||||
peer_addr,
|
||||
target_addr,
|
||||
svr_cfg.tcp_external_addr(),
|
||||
|
||||
@@ -15,7 +15,6 @@ use shadowsocks::{
|
||||
net::{AcceptOpts, TcpStream as OutboundTcpStream},
|
||||
relay::tcprelay::{ProxyServerStream, utils::copy_encrypted_bidirectional},
|
||||
};
|
||||
use socket2::SockRef;
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::TcpStream as TokioTcpStream,
|
||||
@@ -166,7 +165,7 @@ impl TcpServerClient {
|
||||
|
||||
// tokio's TcpStream.set_linger was marked as deprecated.
|
||||
// But we set linger(0), which won't block the thread when close() the socket.
|
||||
let _ = SockRef::from(&stream).set_linger(Some(Duration::ZERO));
|
||||
let _ = socket2::SockRef::from(&stream).set_linger(Some(Duration::ZERO));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -516,7 +516,7 @@ where
|
||||
#[cfg(feature = "stream-cipher")]
|
||||
CipherKind::SS_TABLE => {
|
||||
// TABLE cipher doesn't need key derivation.
|
||||
// Reference implemenation: shadowsocks-libev, shadowsocks (Python)
|
||||
// Reference implementation: shadowsocks-libev, shadowsocks (Python)
|
||||
let enc_key = password.clone().into_bytes().into_boxed_slice();
|
||||
return Ok((password, enc_key, Vec::new()));
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ pub mod android {
|
||||
use sealed::sealed;
|
||||
use std::{fmt, io, os::unix::io::RawFd};
|
||||
|
||||
/// Android VPN socket protect implemetation
|
||||
/// Android VPN socket protect implementation
|
||||
#[sealed]
|
||||
pub trait SocketProtect {
|
||||
/// Protects the socket file descriptor by calling `VpnService.protect(fd)`
|
||||
|
||||
@@ -440,11 +440,11 @@ shadowsocks-rust (1.15.0) unstable; urgency=medium
|
||||
|
||||
- shadowsocks/shadowsocks-org#204 Remove `security-iv-printable-prefix` experimental feature
|
||||
- Upgrade [clap](https://crates.io/crates/clap) to v4.0.
|
||||
- UDP association channel size shrinked to 1024, which could save a lot of memory for each associations
|
||||
- UDP association channel size shrunk to 1024, which could save a lot of memory for each associations
|
||||
|
||||
## Enhancements
|
||||
|
||||
- #855 Properly handle IPv4-mapped-IPv6 addresses in UDP assocations
|
||||
- #855 Properly handle IPv4-mapped-IPv6 addresses in UDP associations
|
||||
- `ssserver` always convert IPv4-mapped-IPv6 addresses to IPv4 in UDP respond target addresses
|
||||
- `sslocal`
|
||||
- `tun`, `socks5` always convert IPv4-mapped-IPv6 addresses to IPv4
|
||||
@@ -476,11 +476,11 @@ shadowsocks-rust (1.14.0) unstable; urgency=medium
|
||||
|
||||
## Features
|
||||
|
||||
- Optimization: UDP associations managment removes unnecessary locks
|
||||
- Optimization: UDP associations management removes unnecessary locks
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- `shadowsocks_service::local::net::udp::UdpAssociationManager::new` returns clean-up interval and keep-alive receiver. It is not recommended to use this directly in your Project, because the API may change occationally.
|
||||
- `shadowsocks_service::local::net::udp::UdpAssociationManager::new` returns clean-up interval and keep-alive receiver. It is not recommended to use this directly in your Project, because the API may change occasionally.
|
||||
|
||||
shadowsocks-rust (1.13.5) unstable; urgency=medium
|
||||
|
||||
@@ -538,7 +538,7 @@ shadowsocks-rust (1.13.0) unstable; urgency=medium
|
||||
- When server start, the "check best" strategy starts after it receives enough data for estimating all servers' scores
|
||||
- #744 Refactored `local-tun`, using `smoltcp` as a user-space network stack
|
||||
|
||||
## Miscelleneous
|
||||
## Miscellaneous
|
||||
|
||||
- Upgrade [`clap`](https://crates.io/crates/clap) (the command-line argument handling library) to v3.0
|
||||
- #739 Support K8S deployment
|
||||
@@ -569,7 +569,7 @@ shadowsocks-rust (1.12.4) unstable; urgency=medium
|
||||
- #694 UDP binds to dual-stack address (`::`) will detect `0.0.0.0` already in use automatically
|
||||
- Transparent Proxy (redir) local client will always set `IPV6_V6ONLY` for listeners that are listening on `::`
|
||||
|
||||
## Miscelleneous
|
||||
## Miscellaneous
|
||||
|
||||
- Deprecating `SS_LOG_VERBOSE_LEVEL` and `SS_LOG_WITHOUT_TIME` (introduced in [v1.12.3](https://github.com/shadowsocks/shadowsocks-rust/releases/tag/v1.12.3)) in flavor of configuring in the configuration file
|
||||
|
||||
@@ -855,7 +855,7 @@ shadowsocks-rust (1.9.0) unstable; urgency=medium
|
||||
* Support setting SO_SNDBUF and SO_RCVBUF for TCP sockets
|
||||
* Support [SIP008](https://github.com/shadowsocks/shadowsocks-org/issues/89 "Online config") extend server fields server, server_port, remarks
|
||||
* Local DNS Relay
|
||||
* Support sending TCP and UDP queries simutaneously
|
||||
* Support sending TCP and UDP queries simultaneously
|
||||
* Support connection reusability
|
||||
* Remove mostly TCP timeout setting for tunnels, connections will only be killed if clients or servers close
|
||||
* Auto-reload DNS resolver configuration from /etc/resolv.conf on *NIX platforms.
|
||||
|
||||
+5
-9
@@ -206,7 +206,7 @@ update_apple_version:
|
||||
update_macos_version:
|
||||
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
|
||||
|
||||
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
||||
release_apple: lib_apple update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
||||
|
||||
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
||||
|
||||
@@ -234,18 +234,14 @@ test_stdio:
|
||||
lib_android:
|
||||
go run ./cmd/internal/build_libbox -target android
|
||||
|
||||
lib_android_debug:
|
||||
go run ./cmd/internal/build_libbox -target android -debug
|
||||
|
||||
lib_apple:
|
||||
go run ./cmd/internal/build_libbox -target apple
|
||||
|
||||
lib_ios:
|
||||
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
|
||||
lib_android_new:
|
||||
go run ./cmd/internal/build_libbox_newffi -target android
|
||||
|
||||
lib:
|
||||
go run ./cmd/internal/build_libbox -target android
|
||||
go run ./cmd/internal/build_libbox -target ios
|
||||
lib_apple_new:
|
||||
go run ./cmd/internal/build_libbox_newffi -target apple
|
||||
|
||||
lib_install:
|
||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.11
|
||||
|
||||
@@ -194,13 +194,13 @@ dependencies {
|
||||
implementation("androidx.appcompat:appcompat:1.7.1")
|
||||
implementation("com.google.android.material:material:1.13.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.9.6")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.9.6")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.9.7")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.9.7")
|
||||
implementation("com.google.zxing:core:3.5.4")
|
||||
implementation("androidx.coordinatorlayout:coordinatorlayout:1.3.0")
|
||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0")
|
||||
implementation("com.blacksquircle.ui:editorkit:2.2.0")
|
||||
implementation("com.blacksquircle.ui:language-json:2.2.0")
|
||||
implementation("com.android.tools.smali:smali-dexlib2:3.0.9") {
|
||||
|
||||
+18
-3
@@ -113,14 +113,19 @@ fun ProfileOverrideScreen(navController: NavController) {
|
||||
val isShizukuBinderReady by PackageQueryManager.shizukuBinderReady.collectAsState()
|
||||
val isShizukuPermissionGranted by PackageQueryManager.shizukuPermissionGranted.collectAsState()
|
||||
val isShizukuAvailable = isShizukuBinderReady && isShizukuPermissionGranted
|
||||
var isShizukuStateInitialized by remember(showModeSelector) { mutableStateOf(!showModeSelector) }
|
||||
|
||||
DisposableEffect(showModeSelector) {
|
||||
if (showModeSelector) {
|
||||
isShizukuStateInitialized = false
|
||||
PackageQueryManager.registerListeners()
|
||||
PackageQueryManager.refreshShizukuState()
|
||||
isShizukuStateInitialized = true
|
||||
}
|
||||
onDispose {
|
||||
if (showModeSelector) {
|
||||
PackageQueryManager.unregisterListeners()
|
||||
isShizukuStateInitialized = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,8 +145,14 @@ fun ProfileOverrideScreen(navController: NavController) {
|
||||
}
|
||||
|
||||
// Auto-disable per-app proxy if Shizuku authorization is revoked (only when using Shizuku mode)
|
||||
LaunchedEffect(isShizukuAvailable, useRootMode) {
|
||||
if (showModeSelector && !useRootMode && !isShizukuAvailable && perAppProxyEnabled) {
|
||||
LaunchedEffect(isShizukuAvailable, useRootMode, isShizukuStateInitialized, perAppProxyEnabled, showModeSelector) {
|
||||
if (
|
||||
showModeSelector &&
|
||||
!useRootMode &&
|
||||
isShizukuStateInitialized &&
|
||||
perAppProxyEnabled &&
|
||||
!PackageQueryManager.isShizukuAvailable()
|
||||
) {
|
||||
perAppProxyEnabled = false
|
||||
withContext(Dispatchers.IO) {
|
||||
Settings.perAppProxyEnabled = false
|
||||
@@ -631,7 +642,11 @@ fun ProfileOverrideScreen(navController: NavController) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
Settings.perAppProxyPackageQueryMode = Settings.PACKAGE_QUERY_MODE_SHIZUKU
|
||||
}
|
||||
if (perAppProxyEnabled && !isShizukuAvailable) {
|
||||
if (
|
||||
perAppProxyEnabled &&
|
||||
isShizukuStateInitialized &&
|
||||
!PackageQueryManager.isShizukuAvailable()
|
||||
) {
|
||||
perAppProxyEnabled = false
|
||||
scope.launch(Dispatchers.IO) {
|
||||
Settings.perAppProxyEnabled = false
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<!-- App -->
|
||||
<string name="app_name" translatable="false">sing-box</string>
|
||||
<string name="app_version_title">نسخه برنامه</string>
|
||||
|
||||
<!-- Common Actions -->
|
||||
<string name="ok">تأیید</string>
|
||||
<string name="cancel">لغو</string>
|
||||
<string name="save">ذخیره</string>
|
||||
<string name="discard">چشمپوشی</string>
|
||||
<string name="edit">ویرایش</string>
|
||||
<string name="close">بستن</string>
|
||||
<string name="dismiss">بستن</string>
|
||||
<string name="stop">توقف</string>
|
||||
<string name="reset">بازنشانی</string>
|
||||
<string name="destroy">حذف</string>
|
||||
<string name="clear">پاک کردن</string>
|
||||
<string name="browse">مرور</string>
|
||||
<string name="search">جستجو</string>
|
||||
<string name="action">اقدام</string>
|
||||
<string name="action_start">شروع</string>
|
||||
<string name="action_deselect">لغو انتخاب</string>
|
||||
<string name="expand">باز کردن</string>
|
||||
<string name="collapse">جمع کردن</string>
|
||||
<string name="expand_all">باز کردن همه</string>
|
||||
<string name="collapse_all">جمع کردن همه</string>
|
||||
<string name="previous">قبلی</string>
|
||||
<string name="next">بعدی</string>
|
||||
<string name="update">بهروزرسانی</string>
|
||||
<string name="read_more">بیشتر بخوانید</string>
|
||||
<string name="no_thanks">نه، ممنون</string>
|
||||
<string name="options">گزینهها</string>
|
||||
<string name="more_options">گزینههای بیشتر</string>
|
||||
|
||||
<!-- Common States -->
|
||||
<string name="enabled">فعال</string>
|
||||
<string name="disabled">غیرفعال</string>
|
||||
<string name="loading">در حال بارگذاری…</string>
|
||||
<string name="calculating">در حال محاسبه...</string>
|
||||
<string name="auto">خودکار</string>
|
||||
<string name="success">موفق</string>
|
||||
<string name="default_text">پیشفرض</string>
|
||||
|
||||
<!-- Navigation Titles -->
|
||||
<string name="title_dashboard">داشبورد</string>
|
||||
<string name="title_configuration">پروفایلها</string>
|
||||
<string name="title_log">گزارشها</string>
|
||||
<string name="title_settings">تنظیمات</string>
|
||||
<string name="title_app_settings">برنامه</string>
|
||||
<string name="title_new_profile">پروفایل جدید</string>
|
||||
<string name="title_edit_profile">ویرایش پروفایل</string>
|
||||
<string name="title_edit_configuration">ویرایش پیکربندی</string>
|
||||
<string name="title_groups">گروهها</string>
|
||||
<string name="title_debug">اشکالزدایی</string>
|
||||
<string name="title_connections">اتصالها</string>
|
||||
<string name="title_others">سایر</string>
|
||||
<string name="title_scan_result">نتیجه اسکن</string>
|
||||
|
||||
<!-- Status -->
|
||||
<string name="status_default">سرویس شروع نشده است</string>
|
||||
<string name="status_starting">در حال شروع</string>
|
||||
<string name="status_stopping">در حال توقف</string>
|
||||
<string name="status_started">شروع شده</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dashboard_items">آیتمهای داشبورد</string>
|
||||
<string name="memory">حافظه</string>
|
||||
<string name="goroutines">گوروتینها</string>
|
||||
<string name="upload">آپلود</string>
|
||||
<string name="download">دانلود</string>
|
||||
<string name="connections_in">ورودی</string>
|
||||
<string name="connections_out">خروجی</string>
|
||||
<string name="clash_mode">حالت Clash</string>
|
||||
<string name="mode">حالت</string>
|
||||
<string name="system_proxy">پراکسی سیستم</string>
|
||||
<string name="url_test">تست</string>
|
||||
<string name="drag_handle_to_reorder">برای جابهجایی آیتمها، دستگیره را بکشید</string>
|
||||
<string name="drag_to_reorder">برای جابهجایی بکشید</string>
|
||||
<string name="reset_order">بازنشانی ترتیب</string>
|
||||
|
||||
<!-- Connections -->
|
||||
<string name="empty_connections">هیچ اتصالی وجود ندارد</string>
|
||||
<string name="search_connections">جستجوی اتصالها…</string>
|
||||
<string name="close_connections_confirm">همه اتصالها بسته شوند؟</string>
|
||||
<string name="connection_state_all">همه</string>
|
||||
<string name="connection_state_active">فعال</string>
|
||||
<string name="connection_state_closed">بسته</string>
|
||||
<string name="connection_sort_date">تاریخ</string>
|
||||
<string name="connection_sort_traffic">ترافیک</string>
|
||||
<string name="connection_sort_traffic_total">ترافیک کل</string>
|
||||
<string name="connection_close">بستن</string>
|
||||
<string name="connection_close_all">بستن همه</string>
|
||||
<string name="connection_state">وضعیت</string>
|
||||
<string name="connection_details">جزئیات اتصال</string>
|
||||
<string name="connection_section_basic">اطلاعات پایه</string>
|
||||
<string name="connection_section_metadata">فراداده</string>
|
||||
<string name="connection_section_process">اطلاعات فرایند</string>
|
||||
<string name="connection_created_at">زمان ایجاد</string>
|
||||
<string name="connection_closed_at">زمان بسته شدن</string>
|
||||
<string name="connection_duration">مدتزمان</string>
|
||||
<string name="connection_uplink">آپلینک</string>
|
||||
<string name="connection_downlink">دانلینک</string>
|
||||
<string name="connection_inbound">ورودی</string>
|
||||
<string name="connection_inbound_type">نوع ورودی</string>
|
||||
<string name="connection_outbound">خروجی</string>
|
||||
<string name="connection_outbound_type">نوع خروجی</string>
|
||||
<string name="connection_ip_version">نسخه IP</string>
|
||||
<string name="connection_network">شبکه</string>
|
||||
<string name="connection_source">مبدأ</string>
|
||||
<string name="connection_destination">مقصد</string>
|
||||
<string name="connection_domain">دامنه</string>
|
||||
<string name="connection_protocol">پروتکل</string>
|
||||
<string name="connection_user">کاربر</string>
|
||||
<string name="connection_from_outbound">از خروجی</string>
|
||||
<string name="connection_match_rule">قانون تطبیق</string>
|
||||
<string name="connection_chain">زنجیره</string>
|
||||
<string name="connection_process_id">شناسه فرایند</string>
|
||||
<string name="connection_user_id">شناسه کاربر</string>
|
||||
<string name="connection_user_name">نام کاربر</string>
|
||||
<string name="connection_process_path">مسیر فرایند</string>
|
||||
<string name="connection_package_name">نام بسته</string>
|
||||
|
||||
<!-- Profiles -->
|
||||
<string name="no_profiles">هیچ پروفایلی تنظیم نشده است</string>
|
||||
<string name="add_profile">افزودن پروفایل</string>
|
||||
<string name="update_profile">بهروزرسانی پروفایل</string>
|
||||
<string name="import_remote_profile">وارد کردن پروفایل راه دور</string>
|
||||
<string name="import_remote_profile_message">آیا از وارد کردن پروفایل راه دور %1$s مطمئن هستید؟ برای دانلود پیکربندی به %2$s متصل میشوید.</string>
|
||||
<string name="import_from_file_description">وارد کردن پیکربندی از فایل محلی</string>
|
||||
<string name="scan_qr_code_description">اسکن QR کد پیکربندی</string>
|
||||
<string name="create_new_profile_description">ایجاد پروفایل جدید از ابتدا</string>
|
||||
<string name="icloud_profile_unsupported">پروفایل iCloud در پلتفرم فعلی پشتیبانی نمیشود</string>
|
||||
<string name="profile_name">نام</string>
|
||||
<string name="profile_type">نوع</string>
|
||||
<string name="profile_source">منبع</string>
|
||||
<string name="profile_url" translatable="false">URL</string>
|
||||
<string name="profile_import_file">وارد کردن فایل</string>
|
||||
<string name="profile_create">ایجاد</string>
|
||||
<string name="profile_share_url">اشتراکگذاری URL بهصورت QR کد</string>
|
||||
<string name="profile_input_required">الزامی</string>
|
||||
<string name="profile_update">بهروزرسانی</string>
|
||||
<string name="profile_auto_update">بهروزرسانی خودکار</string>
|
||||
<string name="profile_auto_update_interval">بازه بهروزرسانی خودکار (دقیقه)</string>
|
||||
<string name="profile_auto_update_interval_minimum_hint">حداقل مقدار 15 است</string>
|
||||
<string name="profile_type_local">محلی</string>
|
||||
<string name="profile_type_remote">راه دور</string>
|
||||
<string name="profile_type_remote_updated">راه دور • %s</string>
|
||||
<string name="profile_source_create_new">ایجاد مورد جدید</string>
|
||||
<string name="profile_source_import">وارد کردن</string>
|
||||
<string name="profile_add_import_file">وارد کردن از فایل</string>
|
||||
<string name="profile_add_scan_qr_code">اسکن QR کد</string>
|
||||
<string name="profile_add_create_manually">ایجاد دستی</string>
|
||||
<string name="profile_override">بازنویسی پروفایل</string>
|
||||
<string name="basic_information">اطلاعات پایه</string>
|
||||
<string name="remote_configuration">پیکربندی راه دور</string>
|
||||
<string name="last_updated_format">آخرین بهروزرسانی: %s</string>
|
||||
<string name="content">محتوا</string>
|
||||
<string name="json_viewer">نمایشگر JSON</string>
|
||||
<string name="json_editor">ویرایشگر JSON</string>
|
||||
<string name="view_configuration">مشاهده پیکربندی</string>
|
||||
<string name="save_as_file">ذخیره بهصورت فایل</string>
|
||||
<string name="share_as_file">اشتراکگذاری بهصورت فایل</string>
|
||||
<string name="save_content_json">ذخیره محتوای JSON</string>
|
||||
<string name="share_content_json">اشتراکگذاری محتوای JSON</string>
|
||||
<string name="unsaved_changes">تغییرات ذخیرهنشده</string>
|
||||
<string name="unsaved_changes_message">تغییرات ذخیرهنشده دارید. میخواهید از آنها صرفنظر کنید؟</string>
|
||||
<string name="import_profile_confirm_title">وارد کردن پروفایل</string>
|
||||
<string name="import_profile_confirm_message">وارد کردن پروفایل \"%s\"؟</string>
|
||||
<string name="import_action">وارد کردن</string>
|
||||
|
||||
<!-- Groups -->
|
||||
<string name="group_selected_title">انتخابشده</string>
|
||||
|
||||
<!-- Log -->
|
||||
<string name="log_level">سطح لاگ</string>
|
||||
<string name="filter_label">فیلتر: %s</string>
|
||||
<string name="clear_filter">پاک کردن</string>
|
||||
<string name="clear_logs">پاک کردن لاگها</string>
|
||||
<string name="search_logs_placeholder">جستجوی لاگها…</string>
|
||||
<string name="logs_copied_to_clipboard">لاگها در کلیپبورد کپی شدند</string>
|
||||
<string name="no_logs_to_copy">لاگی برای کپی وجود ندارد</string>
|
||||
<string name="no_logs_to_share">لاگی برای اشتراکگذاری وجود ندارد</string>
|
||||
<string name="intent_share_logs">اشتراکگذاری لاگها</string>
|
||||
<string name="selected_count">%d انتخاب شده</string>
|
||||
<string name="not_selected">انتخاب نشده</string>
|
||||
<string name="save_to_clipboard">به کلیپبورد</string>
|
||||
<string name="save_to_file">به فایل</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="service">سرویس</string>
|
||||
<string name="core">هسته</string>
|
||||
<string name="core_version_title">نسخه هسته</string>
|
||||
<string name="core_data_size">اندازه داده</string>
|
||||
<string name="about">درباره</string>
|
||||
<string name="source_code">کد منبع</string>
|
||||
<string name="sponsor">حامی مالی</string>
|
||||
<string name="working_directory">پوشه کاری</string>
|
||||
<string name="disable_deprecated_warnings">غیرفعالکردن هشدارهای منسوخ</string>
|
||||
<string name="ignore_memory_limit">نادیده گرفتن محدودیت حافظه</string>
|
||||
<string name="ignore_memory_limit_description">محدودیت حافظه روی sing-box اعمال نشود.</string>
|
||||
<string name="auto_redirect">تغییر مسیر خودکار</string>
|
||||
<string name="auto_redirect_description">نیازمند دسترسی ROOT</string>
|
||||
<string name="system_http_proxy">پراکسی HTTP سیستم</string>
|
||||
<string name="update_settings">تنظیمات بهروزرسانی</string>
|
||||
|
||||
<!-- Per-App Proxy -->
|
||||
<string name="per_app_proxy">پراکسی هر برنامه</string>
|
||||
<string name="per_app_proxy_mode">حالت پراکسی</string>
|
||||
<string name="per_app_proxy_mode_include">شامل</string>
|
||||
<string name="per_app_proxy_mode_include_description">فقط برنامههای انتخابشده از VPN عبور میکنند</string>
|
||||
<string name="per_app_proxy_mode_exclude">استثنا</string>
|
||||
<string name="per_app_proxy_mode_exclude_description">برنامههای انتخابشده از VPN مستثنی میشوند</string>
|
||||
<string name="per_app_proxy_action_copy">کپی</string>
|
||||
<string name="per_app_proxy_action_copy_package_name">نام بسته</string>
|
||||
<string name="per_app_proxy_action_copy_uid">UID</string>
|
||||
<string name="per_app_proxy_sort_mode">مرتبسازی بر اساس</string>
|
||||
<string name="per_app_proxy_sort_mode_name">بر اساس نام</string>
|
||||
<string name="per_app_proxy_sort_mode_package_name">بر اساس نام بسته</string>
|
||||
<string name="per_app_proxy_sort_mode_uid">بر اساس UID</string>
|
||||
<string name="per_app_proxy_sort_mode_install_time">بر اساس زمان نصب</string>
|
||||
<string name="per_app_proxy_sort_mode_update_time">بر اساس زمان بهروزرسانی</string>
|
||||
<string name="per_app_proxy_sort_mode_reverse">معکوس</string>
|
||||
<string name="per_app_proxy_filter">فیلتر</string>
|
||||
<string name="per_app_proxy_hide_system_apps">پنهان کردن برنامههای سیستمی</string>
|
||||
<string name="per_app_proxy_hide_offline_apps">پنهان کردن برنامههای آفلاین</string>
|
||||
<string name="per_app_proxy_hide_disabled_apps">پنهان کردن برنامههای غیرفعال</string>
|
||||
<string name="per_app_proxy_select">انتخاب</string>
|
||||
<string name="per_app_proxy_select_all">انتخاب همه</string>
|
||||
<string name="per_app_proxy_select_none">لغو انتخاب همه</string>
|
||||
<string name="per_app_proxy_backup">پشتیبانگیری</string>
|
||||
<string name="per_app_proxy_import">وارد کردن از کلیپبورد</string>
|
||||
<string name="per_app_proxy_export">خروجی به کلیپبورد</string>
|
||||
<string name="per_app_proxy_scan">اسکن</string>
|
||||
<string name="per_app_proxy_scan_china_apps">برنامههای چینی</string>
|
||||
<string name="per_app_proxy_manage">مدیریت</string>
|
||||
<string name="content_description_app_icon">آیکن برنامه</string>
|
||||
<string name="per_app_proxy_managed_mode">حالت مدیریتشده</string>
|
||||
<string name="per_app_proxy_managed_mode_description">برنامههای چینی را خودکار مستثنی کن</string>
|
||||
<string name="per_app_proxy_package_query_mode">حالت</string>
|
||||
<string name="per_app_proxy_shizuku_required">وقتی از Play Store نصب میشود، پراکسی هر برنامه به Shizuku نیاز دارد. Google Play اجازه استفاده از مجوز QUERY_ALL_PACKAGES را به ما نمیدهد (در حالی که برنامههای مشابه دیگر را منع نمیکند) و این مجوز برای فهرست کردن برنامهها لازم است.</string>
|
||||
<string name="per_app_proxy_root_required">وقتی از Play Store نصب میشود، پراکسی هر برنامه به ROOT نیاز دارد. Google Play اجازه استفاده از مجوز QUERY_ALL_PACKAGES را به ما نمیدهد (در حالی که برنامههای مشابه دیگر را منع نمیکند) و این مجوز برای فهرست کردن برنامهها لازم است.</string>
|
||||
<string name="privileged_access_required">برای دریافت فهرست کامل برنامهها به دسترسی Root یا Shizuku نیاز است</string>
|
||||
|
||||
<!-- Permissions -->
|
||||
<string name="background_permission">مجوز پسزمینه</string>
|
||||
<string name="background_permission_description">برای کارکرد صحیح VPN، مجوزهای لازم را اعطا کنید.\n\nاگر از دستگاهی ساخت شرکتهای چینی استفاده میکنید، ممکن است این کارت بعد از اعطای مجوز ناپدید نشود.</string>
|
||||
<string name="request_background_permission">نادیده گرفتن</string>
|
||||
<string name="location_permission_title">مجوز موقعیت مکانی</string>
|
||||
<string name="location_permission_description">پروفایل شما شامل قوانین مسیریابی <strong><tt>wifi_ssid</tt> یا <tt>wifi_bssid</tt></strong> است. برای کارکرد آنها، sing-box از مجوز <strong>موقعیت مکانی</strong> <strong>در پسزمینه</strong> برای دریافت اطلاعات شبکه Wi-Fi متصل استفاده میکند. این اطلاعات <strong>فقط برای مسیریابی</strong> استفاده میشوند.</string>
|
||||
<string name="location_permission_background_description">در Android 10 و بالاتر، مجوز <strong>موقعیت مکانی پسزمینه</strong> لازم است. برای اعطای مجوز، گزینه <strong>اجازه دائمی</strong> را انتخاب کنید.</string>
|
||||
<string name="notification_permission_title">مجوز اعلان</string>
|
||||
<string name="notification_permission_required_description">بدون مجوز اعلان، sing-box نمیتواند سرعت لحظهای شبکه را نمایش دهد. لطفا پیش از شروع سرویس، مجوز را بدهید یا اعلان سرعت لحظهای شبکه را غیرفعال کنید.</string>
|
||||
<string name="root_access_required">دسترسی Root لازم است</string>
|
||||
<string name="root_access_denied">دسترسی Root رد شد</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="check_update">بررسی بهروزرسانی</string>
|
||||
<string name="check_update_automatic">بررسی خودکار بهروزرسانی</string>
|
||||
<string name="check_update_prompt_play">میخواهید بررسی خودکار بهروزرسانی از **Play Store** فعال شود؟</string>
|
||||
<string name="check_update_prompt_github">میخواهید بررسی خودکار بهروزرسانی از **GitHub** فعال شود؟</string>
|
||||
<string name="update_track">کانال بهروزرسانی</string>
|
||||
<string name="update_track_stable">پایدار</string>
|
||||
<string name="update_track_beta">بتا</string>
|
||||
<string name="update_track_not_supported">کانال فعلی هنوز از بررسی بهروزرسانی پشتیبانی نمیکند</string>
|
||||
<string name="view_release">مشاهده انتشار</string>
|
||||
<string name="downloading">در حال دانلود…</string>
|
||||
<string name="exporting">در حال خروجیگیری…</string>
|
||||
<string name="no_updates_available">بهروزرسانیای موجود نیست</string>
|
||||
<string name="new_version_available">نسخه جدید موجود است: %s</string>
|
||||
<string name="auto_update">بهروزرسانی خودکار</string>
|
||||
<string name="auto_update_description">دانلود و نصب خودکار بهروزرسانیها در پسزمینه</string>
|
||||
|
||||
<!-- Silent Install -->
|
||||
<string name="silent_install">نصب بیصدا</string>
|
||||
<string name="silent_install_description">نصب بهروزرسانی بدون تعامل</string>
|
||||
<string name="silent_install_method">روش نصب</string>
|
||||
<string name="silent_install_verify_failed">%s در دسترس نیست یا دسترسی رد شده است</string>
|
||||
<string name="install_method_package_installer">PackageInstaller</string>
|
||||
<string name="install_method_shizuku">Shizuku</string>
|
||||
<string name="install_method_root">ROOT</string>
|
||||
<string name="package_installer_not_available">مجوز نصب داده نشده است</string>
|
||||
<string name="grant_install_permission">اعطای مجوز نصب</string>
|
||||
<string name="grant_install_permission_description">اجازه نصب برنامه از این منبع</string>
|
||||
|
||||
<!-- Shizuku -->
|
||||
<string name="shizuku_not_available">Shizuku نصب نیست یا در حال اجرا نیست</string>
|
||||
<string name="shizuku_description">Shizuku به برنامهها اجازه میدهد APIهای سیستم را با سطح دسترسی بالاتر مستقیما استفاده کنند</string>
|
||||
<string name="get_shizuku">دریافت Shizuku</string>
|
||||
<string name="start_shizuku">شروع Shizuku</string>
|
||||
<string name="request_shizuku">درخواست Shizuku</string>
|
||||
|
||||
<!-- Time -->
|
||||
<string name="time_just_now">همین الآن</string>
|
||||
<string name="time_yesterday">دیروز</string>
|
||||
<string name="time_now">اکنون</string>
|
||||
<string name="time_yesterday_short">1d</string>
|
||||
<string name="time_minutes_short">%dm</string>
|
||||
<string name="time_hours_short">%dh</string>
|
||||
<string name="time_days_short">%dd</string>
|
||||
<plurals name="time_minutes_ago">
|
||||
<item quantity="one">%d دقیقه پیش</item>
|
||||
<item quantity="other">%d دقیقه پیش</item>
|
||||
</plurals>
|
||||
<plurals name="time_hours_ago">
|
||||
<item quantity="one">%d ساعت پیش</item>
|
||||
<item quantity="other">%d ساعت پیش</item>
|
||||
</plurals>
|
||||
<plurals name="time_days_ago">
|
||||
<item quantity="one">%d روز پیش</item>
|
||||
<item quantity="other">%d روز پیش</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Menu -->
|
||||
<string name="menu_undo">واگرد</string>
|
||||
<string name="menu_redo">انجام دوباره</string>
|
||||
<string name="menu_format">قالببندی</string>
|
||||
<string name="menu_delete">حذف</string>
|
||||
<string name="menu_share">اشتراکگذاری</string>
|
||||
|
||||
<!-- Errors -->
|
||||
<string name="error_title">خطا</string>
|
||||
<string name="error_missing_vpn_permission">مجوز VPN وجود ندارد</string>
|
||||
<string name="error_empty_configuration">پیکربندی خالی است</string>
|
||||
<string name="error_empty_file">فایل خالی است</string>
|
||||
<string name="error_decode_profile">بازگشایی پروفایل ناموفق بود: %s</string>
|
||||
<string name="error_invalid_configuration">پیکربندی sing-box نامعتبر است: %s</string>
|
||||
<string name="error_start_command_server">شروع سرور فرمان</string>
|
||||
<string name="error_create_service">ایجاد سرویس</string>
|
||||
<string name="error_start_service">شروع سرویس</string>
|
||||
<string name="error_deprecated_warning">هشدار منسوخ</string>
|
||||
<string name="error_deprecated_documentation">مستندات</string>
|
||||
<string name="file_manager_missing">دستگاه شما فاقد فایلگزین استاندارد Android است، لطفا یکی مثل Material Files نصب کنید.</string>
|
||||
<string name="no_file_manager">مدیر فایل پیدا نشد</string>
|
||||
|
||||
<!-- Failed Operations -->
|
||||
<string name="failed_save_profile">ذخیره پروفایل ناموفق بود: %s</string>
|
||||
<string name="failed_save_logs">ذخیره لاگها ناموفق بود: %s</string>
|
||||
<string name="failed_share_logs">اشتراکگذاری لاگها ناموفق بود: %s</string>
|
||||
<string name="failed_read_configuration">خواندن پیکربندی ناموفق بود: %s</string>
|
||||
|
||||
<!-- Success Messages -->
|
||||
<string name="success_profile_saved">پروفایل با موفقیت ذخیره شد</string>
|
||||
<string name="success_logs_saved">لاگها با موفقیت ذخیره شدند</string>
|
||||
<string name="success_configuration_saved">پیکربندی ذخیره شد</string>
|
||||
<string name="copied_to_clipboard">در کلیپبورد کپی شد</string>
|
||||
|
||||
<!-- Toast Messages -->
|
||||
<string name="toast_clipboard_empty">کلیپبورد خالی است</string>
|
||||
<string name="toast_copied_to_clipboard">در کلیپبورد ذخیره شد</string>
|
||||
<string name="toast_imported_from_clipboard">از کلیپبورد وارد شد</string>
|
||||
|
||||
<!-- Scanning -->
|
||||
<string name="profile_add_scan_use_front_camera">دوربین جلو</string>
|
||||
<string name="profile_add_scan_use_vendor_analyzer">تحلیلگر MLKit</string>
|
||||
<string name="profile_add_scan_enable_torch">چراغقوه</string>
|
||||
<string name="message_scanning">در حال اسکن…</string>
|
||||
<string name="message_scan_app_no_apps_found">هیچ برنامهٔ منطبقی پیدا نشد</string>
|
||||
<string name="message_scan_app_found">برنامههای زیر پیدا شدند، لطفا اقدام مورد نظر را انتخاب کنید.</string>
|
||||
|
||||
<!-- Icon Selection -->
|
||||
<string name="icon">آیکن</string>
|
||||
<string name="icon_count_format">%d آیکن</string>
|
||||
<string name="no_icons_found">آیکنی پیدا نشد</string>
|
||||
<string name="no_icons_match">هیچ آیکنی با \"%s\" مطابقت ندارد</string>
|
||||
<string name="profile_icon">آیکن پروفایل</string>
|
||||
<string name="select_icon">انتخاب آیکن</string>
|
||||
<string name="select_profile_icon">انتخاب آیکن پروفایل</string>
|
||||
<string name="search_icons_placeholder">جستجوی آیکنها...</string>
|
||||
<string name="search_icons">جستجوی آیکنها</string>
|
||||
<string name="close_search">بستن جستجو</string>
|
||||
<string name="current_icon_format">فعلی: %s</string>
|
||||
<string name="categories">دستهها</string>
|
||||
<string name="all_icons">همه آیکنها</string>
|
||||
<string name="back_to_categories">بازگشت به دستهها</string>
|
||||
|
||||
<!-- QR Code -->
|
||||
|
||||
<!-- QR Stream (QRS) -->
|
||||
<string name="share_as_qrs">اشتراکگذاری بهصورت QR Stream</string>
|
||||
<string name="qrs_fps">FPS</string>
|
||||
<string name="qrs_slice_size">اندازه قطعه</string>
|
||||
<string name="qrs_what_is_qrs">QRS چیست</string>
|
||||
|
||||
<!-- Search -->
|
||||
<string name="search_placeholder">یافتن در سند</string>
|
||||
|
||||
<!-- Content Descriptions (Accessibility) -->
|
||||
<string name="content_description_back">بازگشت</string>
|
||||
<string name="content_description_scroll_to_bottom">پیمایش به پایین</string>
|
||||
<string name="content_description_exit_selection_mode">خروج از حالت انتخاب</string>
|
||||
<string name="content_description_copy_selected">کپی موارد انتخابشده</string>
|
||||
<string name="content_description_clear_search">پاک کردن جستجو</string>
|
||||
<string name="content_description_qr_code">QR کد</string>
|
||||
<string name="content_description_resume_logs">ادامه لاگها</string>
|
||||
<string name="content_description_pause_logs">مکث لاگها</string>
|
||||
<string name="content_description_collapse_search">جمع کردن جستجو</string>
|
||||
<string name="content_description_search_logs">جستجوی لاگها</string>
|
||||
|
||||
<!-- Xposed Module -->
|
||||
<string name="xposed_description">بهبود دسترسی ویژه برای sing-box</string>
|
||||
|
||||
<!-- Privileged Enhancement -->
|
||||
<string name="privilege_module_title">ماژولهای دسترسی ویژه</string>
|
||||
<string name="lsposed_module_activated">ماژول LSPosed فعال است</string>
|
||||
<string name="lsposed_module_pending_update">بهروزرسانی ماژول LSPosed در انتظار است</string>
|
||||
<string name="lsposed_module_pending_downgrade">دانگرید ماژول LSPosed در انتظار است</string>
|
||||
<string name="lsposed_module_not_activated">ماژول LSPosed فعال نیست</string>
|
||||
<string name="privilege_settings">بهبود دسترسی ویژه</string>
|
||||
<string name="privilege_settings_hide_title">پنهانسازی تنظیمات</string>
|
||||
<string name="privilege_settings_hide_description">مقاومت در برابر تشخیص VPN برای برنامههای انتخابشده</string>
|
||||
<string name="privilege_settings_hide_manage">مدیریت</string>
|
||||
<string name="privilege_settings_hide_test">اجرای تست</string>
|
||||
<string name="privilege_settings_hide_test_result">نتیجه تست</string>
|
||||
<string name="privilege_settings_hide_test_running">در حال اجرای تستهای تشخیص…</string>
|
||||
<string name="privilege_settings_hide_test_not_detected">تشخیص داده نشد</string>
|
||||
<string name="privilege_settings_interface_rename_title">تغییر نام رابط</string>
|
||||
<string name="privilege_settings_interface_prefix">پیشوند رابط</string>
|
||||
<string name="privilege_settings_vpn_detection_title">تشخیص VPN</string>
|
||||
<string name="privilege_settings_view_logs">مشاهده لاگها</string>
|
||||
<string name="privilege_settings_export_debug">خروجی گرفتن اطلاعات اشکالزدایی</string>
|
||||
<string name="privilege_settings_hook_logs_empty">لاگی وجود ندارد</string>
|
||||
<string name="privilege_settings_export_debug_complete">خروجیگیری کامل شد</string>
|
||||
<string name="privilege_settings_export_debug_message">این فایل حاوی اطلاعات خصوصی است و نباید عمومی به اشتراک گذاشته شود. اندازه: %s</string>
|
||||
<string name="privilege_settings_export_debug_failed">خروجی گرفتن اطلاعات اشکالزدایی ناموفق بود: %s</string>
|
||||
<string name="privilege_settings_risky_app_title">هشدار</string>
|
||||
<string name="privilege_settings_risky_vpn_message_single">این برنامه یک برنامه VPN است. مقاومت در برابر تشخیص VPN ممکن است مشکل ایجاد کند: %1$s</string>
|
||||
<string name="privilege_settings_risky_vpn_message_multi">این برنامهها VPN هستند. مقاومت در برابر تشخیص VPN ممکن است مشکل ایجاد کند: %1$s</string>
|
||||
<string name="privilege_settings_risky_management_message_single">این برنامه مجوز مدیریت شبکه دارد. مقاومت در برابر تشخیص VPN ممکن است مشکل ایجاد کند: %1$s</string>
|
||||
<string name="privilege_settings_risky_management_message_multi">این برنامهها مجوز مدیریت شبکه دارند. مقاومت در برابر تشخیص VPN ممکن است مشکل ایجاد کند: %1$s</string>
|
||||
<string name="privilege_module_restart_action">راهاندازی مجدد</string>
|
||||
<string name="privilege_module_restart_failed">درخواست راهاندازی مجدد ناموفق بود: %1$s</string>
|
||||
<string name="privilege_module_restart_notification_title">نیاز به راهاندازی مجدد</string>
|
||||
<string name="privilege_module_restart_notification_message">ماژول LSPosed بهروزرسانی شد. برای اعمال تغییرات دستگاه را راهاندازی مجدد کنید.</string>
|
||||
<string name="privilege_module_restart_channel">ماژول LSPosed</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,442 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<!-- App -->
|
||||
<string name="app_name" translatable="false">sing-box</string>
|
||||
<string name="app_version_title">Версия приложения</string>
|
||||
|
||||
<!-- Common Actions -->
|
||||
<string name="ok">OK</string>
|
||||
<string name="cancel">Отмена</string>
|
||||
<string name="save">Сохранить</string>
|
||||
<string name="discard">Отказаться</string>
|
||||
<string name="edit">Редактировать</string>
|
||||
<string name="close">Закрыть</string>
|
||||
<string name="dismiss">Закрыть</string>
|
||||
<string name="stop">Остановить</string>
|
||||
<string name="reset">Сброс</string>
|
||||
<string name="destroy">Уничтожить</string>
|
||||
<string name="clear">Очистить</string>
|
||||
<string name="browse">Просмотр</string>
|
||||
<string name="search">Поиск</string>
|
||||
<string name="action">Действие</string>
|
||||
<string name="action_start">Начать</string>
|
||||
<string name="action_deselect">Отменить выбор</string>
|
||||
<string name="expand">Развернуть</string>
|
||||
<string name="collapse">Свернуть</string>
|
||||
<string name="expand_all">Развернуть все</string>
|
||||
<string name="collapse_all">Свернуть все</string>
|
||||
<string name="previous">Предыдущий</string>
|
||||
<string name="next">Следующий</string>
|
||||
<string name="update">Обновление</string>
|
||||
<string name="read_more">Подробнее</string>
|
||||
<string name="no_thanks">Нет, спасибо</string>
|
||||
<string name="options">Настройки</string>
|
||||
<string name="more_options">Дополнительные параметры</string>
|
||||
|
||||
<!-- Common States -->
|
||||
<string name="enabled">Включено</string>
|
||||
<string name="disabled">Выключено</string>
|
||||
<string name="loading">Загрузка...</string>
|
||||
<string name="calculating">Вычисление...</string>
|
||||
<string name="auto">Авто</string>
|
||||
<string name="success">Успешно</string>
|
||||
<string name="default_text">По умолчанию</string>
|
||||
|
||||
<!-- Navigation Titles -->
|
||||
<string name="title_dashboard">Главная</string>
|
||||
<string name="title_configuration">Профили</string>
|
||||
<string name="title_log">Логи</string>
|
||||
<string name="title_settings">Параметры</string>
|
||||
<string name="title_app_settings">Приложение</string>
|
||||
<string name="title_new_profile">Новый профиль</string>
|
||||
<string name="title_edit_profile">Редактировать профиль</string>
|
||||
<string name="title_edit_configuration">Редактировать конфигурацию</string>
|
||||
<string name="title_groups">Группы</string>
|
||||
<string name="title_debug">Отладка</string>
|
||||
<string name="title_connections">Соединения</string>
|
||||
<string name="title_others">Другое</string>
|
||||
<string name="title_scan_result">Результат сканирования</string>
|
||||
|
||||
<!-- Status -->
|
||||
<string name="status_default">Служба не запущена</string>
|
||||
<string name="status_starting">Запуск</string>
|
||||
<string name="status_stopping">Остановка</string>
|
||||
<string name="status_started">Запущена</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dashboard_items">Элементы главной страницы</string>
|
||||
<string name="memory">Память</string>
|
||||
<string name="goroutines">Подпрограммы</string>
|
||||
<string name="upload">Отправка</string>
|
||||
<string name="download">Получение</string>
|
||||
<string name="connections_in">Входящие</string>
|
||||
<string name="connections_out">Исходящие</string>
|
||||
<string name="clash_mode">Режим Clash</string>
|
||||
<string name="mode">Режим</string>
|
||||
<string name="system_proxy">Системный прокси</string>
|
||||
<string name="url_test">Тест</string>
|
||||
<string name="drag_handle_to_reorder">Перетащите ползунок, чтобы изменить порядок элементов</string>
|
||||
<string name="drag_to_reorder">Перетащите, чтобы изменить порядок</string>
|
||||
<string name="reset_order">Сбросить порядок</string>
|
||||
|
||||
<!-- Connections -->
|
||||
<string name="empty_connections">Нет подключений</string>
|
||||
<string name="search_connections">Поиск подключений...</string>
|
||||
<string name="close_connections_confirm">Закрыть все соединения?</string>
|
||||
<string name="connection_state_all">Все</string>
|
||||
<string name="connection_state_active">Активно</string>
|
||||
<string name="connection_state_closed">Закрыто</string>
|
||||
<string name="connection_sort_date">Дата</string>
|
||||
<string name="connection_sort_traffic">Трафик</string>
|
||||
<string name="connection_sort_traffic_total">Общий трафик</string>
|
||||
<string name="connection_close">Закрыть</string>
|
||||
<string name="connection_close_all">Закрыть все</string>
|
||||
<string name="connection_state">Состояние</string>
|
||||
<string name="connection_details">Данные подключения</string>
|
||||
<string name="connection_section_basic">Основная информация</string>
|
||||
<string name="connection_section_metadata">Метаданные</string>
|
||||
<string name="connection_section_process">Информация о процессе</string>
|
||||
<string name="connection_created_at">Создано в</string>
|
||||
<string name="connection_closed_at">Закрыто в</string>
|
||||
<string name="connection_duration">Продолжительность</string>
|
||||
<string name="connection_uplink">Восходящий канал</string>
|
||||
<string name="connection_downlink">Нисходящий канал</string>
|
||||
<string name="connection_inbound">Входящий</string>
|
||||
<string name="connection_inbound_type">Входящий тип</string>
|
||||
<string name="connection_outbound">Исходящий</string>
|
||||
<string name="connection_outbound_type">Исходящий тип</string>
|
||||
<string name="connection_ip_version">Версия IP</string>
|
||||
<string name="connection_network">Сеть</string>
|
||||
<string name="connection_source">Источник</string>
|
||||
<string name="connection_destination">Место назначения</string>
|
||||
<string name="connection_domain">Домен</string>
|
||||
<string name="connection_protocol">Протокол</string>
|
||||
<string name="connection_user">Пользователь</string>
|
||||
<string name="connection_from_outbound">Из исходящих</string>
|
||||
<string name="connection_match_rule">Правило соответствия</string>
|
||||
<string name="connection_chain">Цепь</string>
|
||||
<string name="connection_process_id">ID процесса</string>
|
||||
<string name="connection_user_id">ID пользователя</string>
|
||||
<string name="connection_user_name">Имя пользователя</string>
|
||||
<string name="connection_process_path">Путь процесса</string>
|
||||
<string name="connection_package_name">Название пакета</string>
|
||||
|
||||
<!-- Profiles -->
|
||||
<string name="no_profiles">Профили не настроены</string>
|
||||
<string name="add_profile">Добавить профиль</string>
|
||||
<string name="update_profile">Обновить профиль</string>
|
||||
<string name="import_remote_profile">Импортировать удалённый профиль</string>
|
||||
<string name="import_remote_profile_message">Импортировать удалённый профиль %1$s? Для загрузки конфигурации будет установлено соединение с %2$s.</string>
|
||||
<string name="import_from_file_description">Импорт конфигурации из локального файла</string>
|
||||
<string name="scan_qr_code_description">Сканирование QR-кода с конфигурацией</string>
|
||||
<string name="create_new_profile_description">Создать новый профиль с нуля</string>
|
||||
<string name="icloud_profile_unsupported">Профили iCloud не поддерживаются на текущей платформе</string>
|
||||
<string name="profile_name">Имя</string>
|
||||
<string name="profile_type">Тип</string>
|
||||
<string name="profile_source">Источник</string>
|
||||
<string name="profile_url" translatable="false">URL</string>
|
||||
<string name="profile_import_file">Импортировать файл</string>
|
||||
<string name="profile_create">Создать</string>
|
||||
<string name="profile_share_url">Поделиться URL в виде QR-кода</string>
|
||||
<string name="profile_input_required">Обязательно</string>
|
||||
<string name="profile_update">Обновить</string>
|
||||
<string name="profile_auto_update">Автообновление</string>
|
||||
<string name="profile_auto_update_interval">Интервал автообновления (минуты)</string>
|
||||
<string name="profile_auto_update_interval_minimum_hint">Минимальное значение - 15</string>
|
||||
<string name="profile_type_local">Локальный</string>
|
||||
<string name="profile_type_remote">Удалённый</string>
|
||||
<string name="profile_type_remote_updated">Удалённый • %s</string>
|
||||
<string name="profile_source_create_new">Создать новый</string>
|
||||
<string name="profile_source_import">Импорт</string>
|
||||
<string name="profile_add_import_file">Импорт из файла</string>
|
||||
<string name="profile_add_scan_qr_code">Сканировать QR-код</string>
|
||||
<string name="profile_add_create_manually">Создать вручную</string>
|
||||
<string name="profile_override">Перезапись профиля</string>
|
||||
<string name="basic_information">Основная информация</string>
|
||||
<string name="remote_configuration">Удалённая конфигурация</string>
|
||||
<string name="last_updated_format">Последнее обновление: %s</string>
|
||||
<string name="content">Содержимое</string>
|
||||
<string name="json_viewer">Просмотр JSON</string>
|
||||
<string name="json_editor">Редактор JSON</string>
|
||||
<string name="view_configuration">Просмотреть конфигурацию</string>
|
||||
<string name="save_as_file">Сохранить как файл</string>
|
||||
<string name="share_as_file">Поделиться как файлом</string>
|
||||
<string name="save_content_json">Сохранить содержимое в файл JSON</string>
|
||||
<string name="share_content_json">Поделиться содержимым в файле JSON</string>
|
||||
<string name="unsaved_changes">Несохранённые изменения</string>
|
||||
<string name="unsaved_changes_message">Есть несохранённые изменения. Отменить их?</string>
|
||||
<string name="import_profile_confirm_title">Импорт профиля</string>
|
||||
<string name="import_profile_confirm_message">Импортировать профиль \'%s\'?</string>
|
||||
<string name="import_action">Импортировать</string>
|
||||
|
||||
<!-- Groups -->
|
||||
<string name="group_selected_title">Выбрано</string>
|
||||
|
||||
<!-- Log -->
|
||||
<string name="log_level">Уровень логов</string>
|
||||
<string name="filter_label">Фильтр: %s</string>
|
||||
<string name="clear_filter">Очистить</string>
|
||||
<string name="clear_logs">Очистить логи</string>
|
||||
<string name="search_logs_placeholder">Поиск в логах...</string>
|
||||
<string name="logs_copied_to_clipboard">Лог скопирован в буфер обмена</string>
|
||||
<string name="no_logs_to_copy">Нет записей для копирования</string>
|
||||
<string name="no_logs_to_share">Нет записей для отправки</string>
|
||||
<string name="intent_share_logs">Отправить лог</string>
|
||||
<string name="selected_count">Выбрано: %d</string>
|
||||
<string name="not_selected">Не выбрано</string>
|
||||
<string name="save_to_clipboard">В буфер обмена</string>
|
||||
<string name="save_to_file">В файл</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="service">Служба</string>
|
||||
<string name="core">Ядро</string>
|
||||
<string name="core_version_title">Версия ядра</string>
|
||||
<string name="core_data_size">Размер данных</string>
|
||||
<string name="about">О приложении</string>
|
||||
<string name="source_code">Исходный код</string>
|
||||
<string name="sponsor">Поддержать</string>
|
||||
<string name="working_directory">Рабочая директория</string>
|
||||
<string name="disable_deprecated_warnings">Отключить предупреждения об устаревании</string>
|
||||
<string name="ignore_memory_limit">Игнорировать ограничение памяти</string>
|
||||
<string name="ignore_memory_limit_description">Не применять ограничения по памяти для sing-box.</string>
|
||||
<string name="auto_redirect">Автоматическое перенаправление</string>
|
||||
<string name="auto_redirect_description">Требуются права ROOT</string>
|
||||
<string name="system_http_proxy">Системный HTTP-прокси</string>
|
||||
<string name="update_settings">Настройки обновлений</string>
|
||||
|
||||
<!-- Per-App Proxy -->
|
||||
<string name="per_app_proxy">Прокси для отдельных приложений</string>
|
||||
<string name="per_app_proxy_mode">Режим прокси</string>
|
||||
<string name="per_app_proxy_mode_include">Включить</string>
|
||||
<string name="per_app_proxy_mode_include_description">Только выбранные приложения будут использовать VPN</string>
|
||||
<string name="per_app_proxy_mode_exclude">Исключить</string>
|
||||
<string name="per_app_proxy_mode_exclude_description">Выбранные приложения не будут использовать VPN</string>
|
||||
<string name="per_app_proxy_action_copy">Копировать</string>
|
||||
<string name="per_app_proxy_action_copy_package_name">Имя пакета</string>
|
||||
<string name="per_app_proxy_action_copy_uid">Уникальный ID</string>
|
||||
<string name="per_app_proxy_sort_mode">Сортировка</string>
|
||||
<string name="per_app_proxy_sort_mode_name">По имени</string>
|
||||
<string name="per_app_proxy_sort_mode_package_name">По имени пакета</string>
|
||||
<string name="per_app_proxy_sort_mode_uid">По уникальному ID</string>
|
||||
<string name="per_app_proxy_sort_mode_install_time">По дате установки</string>
|
||||
<string name="per_app_proxy_sort_mode_update_time">По дате обновления</string>
|
||||
<string name="per_app_proxy_sort_mode_reverse">В обратном порядке</string>
|
||||
<string name="per_app_proxy_filter">Фильтр</string>
|
||||
<string name="per_app_proxy_hide_system_apps">Скрыть системные приложения</string>
|
||||
<string name="per_app_proxy_hide_offline_apps">Скрыть оффлайн-приложения</string>
|
||||
<string name="per_app_proxy_hide_disabled_apps">Скрыть отключённые приложения</string>
|
||||
<string name="per_app_proxy_select">Выбрать</string>
|
||||
<string name="per_app_proxy_select_all">Выбрать все</string>
|
||||
<string name="per_app_proxy_select_none">Снять выделение</string>
|
||||
<string name="per_app_proxy_backup">Резервная копия</string>
|
||||
<string name="per_app_proxy_import">Импорт из буфера обмена</string>
|
||||
<string name="per_app_proxy_export">Экспорт в буфер обмена</string>
|
||||
<string name="per_app_proxy_scan">Сканирование</string>
|
||||
<string name="per_app_proxy_scan_china_apps">Китайские приложения</string>
|
||||
<string name="per_app_proxy_manage">Управление</string>
|
||||
<string name="content_description_app_icon">Значок приложения</string>
|
||||
<string name="per_app_proxy_managed_mode">Управляемый режим</string>
|
||||
<string name="per_app_proxy_managed_mode_description">Автоматически исключать китайские приложения</string>
|
||||
<string name="per_app_proxy_package_query_mode">Режим</string>
|
||||
<string name="per_app_proxy_shizuku_required">При установке из Play Store для работы прокси для каждого приложения требуется Shizuku. Google Play не позволяет нам использовать разрешение QUERY_ALL_PACKAGES (хотя другим подобным приложениям это разрешено), которое необходимо для отображения списка приложений.</string>
|
||||
<string name="per_app_proxy_root_required">При установке из Play Store для функции прокси для отдельных приложений требуются права ROOT. Google Play не разрешает нам использовать разрешение QUERY_ALL_PACKAGES (хотя другим подобным приложениям это разрешено), которое необходимо для отображения списка приложений.</string>
|
||||
<string name="privileged_access_required">Для полного списка приложений необходимы права ROOT или доступ через Shizuku</string>
|
||||
|
||||
<!-- Permissions -->
|
||||
<string name="background_permission">Фоновое разрешение</string>
|
||||
<string name="background_permission_description">Предоставить необходимые разрешения для корректной работы VPN.\n\nЕсли Вы используете устройство китайского производителя, уведомление может не исчезнуть даже после предоставления разрешения.</string>
|
||||
<string name="request_background_permission">Игнорировать оптимизацию батареи</string>
|
||||
<string name="location_permission_title">Разрешение на определение местоположения</string>
|
||||
<string name="location_permission_description">Ваш профиль содержит правила маршрутизации <strong><tt>wifi_ssid</tt> или <tt>wifi_bssid</tt></strong>. Для их работы sing-box использует разрешение на <strong>определение местоположения</strong> <strong>в фоновом режиме</strong>, чтобы получить информацию о подключённой сети Wi-Fi. Эта информация будет использоваться <strong>исключительно для маршрутизации</strong>.</string>
|
||||
<string name="location_permission_background_description">В Android 10 и новее требуется разрешение на <strong>определение местоположения в фоне</strong>. Выберите <strong>«Разрешить всегда»</strong> для предоставления этого разрешения.</string>
|
||||
<string name="notification_permission_title">Разрешение на уведомления</string>
|
||||
<string name="notification_permission_required_description">Без разрешения на отправку уведомлений sing-box не сможет отображать скорость сети в реальном времени. Предоставьте разрешение или отключите уведомления о скорости перед запуском службы.</string>
|
||||
<string name="root_access_required">Требуются права ROOT</string>
|
||||
<string name="root_access_denied">Доступ ROOT запрещен</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="check_update">Проверить обновления</string>
|
||||
<string name="check_update_automatic">Автоматическая проверка обновлений</string>
|
||||
<string name="check_update_prompt_play">Включить автоматическую проверку обновлений через **Google Play**?</string>
|
||||
<string name="check_update_prompt_github">Включить автоматическую проверку обновлений через **GitHub**?</string>
|
||||
<string name="update_track">Канал обновлений</string>
|
||||
<string name="update_track_stable">Стабильный</string>
|
||||
<string name="update_track_beta">Бета</string>
|
||||
<string name="update_track_not_supported">Этот канал пока не поддерживает проверку обновлений</string>
|
||||
<string name="view_release">Посмотреть релиз</string>
|
||||
<string name="downloading">Загрузка...</string>
|
||||
<string name="exporting">Экспорт...</string>
|
||||
<string name="no_updates_available">Обновлений нет</string>
|
||||
<string name="new_version_available">Доступна новая версия: %s</string>
|
||||
<string name="auto_update">Автообновление</string>
|
||||
<string name="auto_update_description">Автоматически загружать и устанавливать обновления в фоне</string>
|
||||
|
||||
<!-- Silent Install -->
|
||||
<string name="silent_install">Тихая установка</string>
|
||||
<string name="silent_install_description">Устанавливать обновления без Вашего участия</string>
|
||||
<string name="silent_install_method">Метод установки</string>
|
||||
<string name="silent_install_verify_failed">%s недоступен или доступ запрещен</string>
|
||||
<string name="install_method_package_installer">PackageInstaller</string>
|
||||
<string name="install_method_shizuku">Shizuku</string>
|
||||
<string name="install_method_root">ROOT</string>
|
||||
<string name="package_installer_not_available">Разрешение на установку не предоставлено</string>
|
||||
<string name="grant_install_permission">Предоставить разрешение на установку</string>
|
||||
<string name="grant_install_permission_description">Разрешить установку приложений из этого источника</string>
|
||||
|
||||
<!-- Shizuku -->
|
||||
<string name="shizuku_not_available">Shizuku не установлен или не запущен</string>
|
||||
<string name="shizuku_description">Shizuku позволяет приложениям напрямую использовать системные API с повышенными привилегиями</string>
|
||||
<string name="get_shizuku">Установить Shizuku</string>
|
||||
<string name="start_shizuku">Запустить Shizuku</string>
|
||||
<string name="request_shizuku">Запросить Shizuku</string>
|
||||
|
||||
<!-- Time -->
|
||||
<string name="time_just_now">Только что</string>
|
||||
<string name="time_yesterday">Вчера</string>
|
||||
<string name="time_now">Сейчас</string>
|
||||
<string name="time_yesterday_short">1 день</string>
|
||||
<string name="time_minutes_short">%d мин.</string>
|
||||
<string name="time_hours_short">%d ч.</string>
|
||||
<string name="time_days_short">%d дн.</string>
|
||||
<plurals name="time_minutes_ago">
|
||||
<item quantity="one">%d минуту назад</item>
|
||||
<item quantity="few">%d минуты назад</item>
|
||||
<item quantity="many">%d минут назад</item>
|
||||
<item quantity="other">%d минут назад</item>
|
||||
</plurals>
|
||||
<plurals name="time_hours_ago">
|
||||
<item quantity="one">%d час назад</item>
|
||||
<item quantity="few">%d часа назад</item>
|
||||
<item quantity="many">%d часов назад</item>
|
||||
<item quantity="other">%d часов назад</item>
|
||||
</plurals>
|
||||
<plurals name="time_days_ago">
|
||||
<item quantity="one">%d день назад</item>
|
||||
<item quantity="few">%d дня назад</item>
|
||||
<item quantity="many">%d дней назад</item>
|
||||
<item quantity="other">%d дней назад</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Menu -->
|
||||
<string name="menu_undo">Отменить</string>
|
||||
<string name="menu_redo">Повторить</string>
|
||||
<string name="menu_format">Форматировать</string>
|
||||
<string name="menu_delete">Удалить</string>
|
||||
<string name="menu_share">Поделиться</string>
|
||||
|
||||
<!-- Errors -->
|
||||
<string name="error_title">Ошибка</string>
|
||||
<string name="error_missing_vpn_permission">Отсутствует разрешение на VPN</string>
|
||||
<string name="error_empty_configuration">Пустая конфигурация</string>
|
||||
<string name="error_empty_file">Пустой файл</string>
|
||||
<string name="error_decode_profile">Не удалось декодировать профиль: %s</string>
|
||||
<string name="error_invalid_configuration">Недопустимая конфигурация sing-box: %s</string>
|
||||
<string name="error_start_command_server">Запуск сервера команд</string>
|
||||
<string name="error_create_service">Создание службы</string>
|
||||
<string name="error_start_service">Запуск службы</string>
|
||||
<string name="error_deprecated_warning">Предупреждение об устаревании</string>
|
||||
<string name="error_deprecated_documentation">Документация</string>
|
||||
<string name="file_manager_missing">На Вашем устройстве отсутствует стандартный файловый менеджер Android. Установите, например, Material Files.</string>
|
||||
<string name="no_file_manager">Файловый менеджер не найден</string>
|
||||
|
||||
<!-- Failed Operations -->
|
||||
<string name="failed_save_profile">Не удалось сохранить профиль: %s</string>
|
||||
<string name="failed_save_logs">Не удалось сохранить лог: %s</string>
|
||||
<string name="failed_share_logs">Не удалось отправить лог: %s</string>
|
||||
<string name="failed_read_configuration">Не удалось прочитать конфигурацию: %s</string>
|
||||
|
||||
<!-- Success Messages -->
|
||||
<string name="success_profile_saved">Профиль успешно сохранён</string>
|
||||
<string name="success_logs_saved">Лог успешно сохранён</string>
|
||||
<string name="success_configuration_saved">Конфигурация сохранена</string>
|
||||
<string name="copied_to_clipboard">Скопировано в буфер обмена</string>
|
||||
|
||||
<!-- Toast Messages -->
|
||||
<string name="toast_clipboard_empty">Буфер обмена пуст</string>
|
||||
<string name="toast_copied_to_clipboard">Экспортировано в буфер обмена</string>
|
||||
<string name="toast_imported_from_clipboard">Импортировано из буфера обмена</string>
|
||||
|
||||
<!-- Scanning -->
|
||||
<string name="profile_add_scan_use_front_camera">Фронтальная камера</string>
|
||||
<string name="profile_add_scan_use_vendor_analyzer">Анализатор MLKit</string>
|
||||
<string name="profile_add_scan_enable_torch">Фонарик</string>
|
||||
<string name="message_scanning">Сканирование…</string>
|
||||
<string name="message_scan_app_no_apps_found">Совпадающих приложений не найдено</string>
|
||||
<string name="message_scan_app_found">Найдены следующие приложения. Выберите нужное действие.</string>
|
||||
|
||||
<!-- Icon Selection -->
|
||||
<string name="icon">Иконка</string>
|
||||
<string name="icon_count_format">%d иконок</string>
|
||||
<string name="no_icons_found">Иконки не найдены</string>
|
||||
<string name="no_icons_match">Нет иконок, соответствующих \'%s\'</string>
|
||||
<string name="profile_icon">Иконка профиля</string>
|
||||
<string name="select_icon">Выбрать иконку</string>
|
||||
<string name="select_profile_icon">Выбрать иконку профиля</string>
|
||||
<string name="search_icons_placeholder">Поиск иконок...</string>
|
||||
<string name="search_icons">Поиск иконок</string>
|
||||
<string name="close_search">Закрыть поиск</string>
|
||||
<string name="current_icon_format">Текущая: %s</string>
|
||||
<string name="categories">Категории</string>
|
||||
<string name="all_icons">Все иконки</string>
|
||||
<string name="back_to_categories">Назад к категориям</string>
|
||||
|
||||
<!-- QR Code -->
|
||||
|
||||
<!-- QR Stream (QRS) -->
|
||||
<string name="share_as_qrs">Отправить как QR Stream</string>
|
||||
<string name="qrs_fps">Кадров/сек.</string>
|
||||
<string name="qrs_slice_size">Размер фрагмента</string>
|
||||
<string name="qrs_what_is_qrs">Что такое QRS</string>
|
||||
|
||||
<!-- Search -->
|
||||
<string name="search_placeholder">Найти в документе</string>
|
||||
|
||||
<!-- Content Descriptions (Accessibility) -->
|
||||
<string name="content_description_back">Назад</string>
|
||||
<string name="content_description_scroll_to_bottom">Прокрутить вниз</string>
|
||||
<string name="content_description_exit_selection_mode">Выйти из режима выбора</string>
|
||||
<string name="content_description_copy_selected">Копировать выделенное</string>
|
||||
<string name="content_description_clear_search">Очистить поиск</string>
|
||||
<string name="content_description_qr_code">QR-код</string>
|
||||
<string name="content_description_resume_logs">Возобновить лог</string>
|
||||
<string name="content_description_pause_logs">Приостановить лог</string>
|
||||
<string name="content_description_collapse_search">Свернуть поиск</string>
|
||||
<string name="content_description_search_logs">Поиск в логе</string>
|
||||
|
||||
<!-- Xposed Module -->
|
||||
<string name="xposed_description">Привилегированное расширение для sing-box</string>
|
||||
|
||||
<!-- Privileged Enhancement -->
|
||||
<string name="privilege_module_title">Привилегированные модули</string>
|
||||
<string name="lsposed_module_activated">Модуль LSPosed активирован</string>
|
||||
<string name="lsposed_module_pending_update">Ожидается обновление модуля LSPosed</string>
|
||||
<string name="lsposed_module_pending_downgrade">Ожидается откат модуля LSPosed</string>
|
||||
<string name="lsposed_module_not_activated">Модуль LSPosed не активирован</string>
|
||||
<string name="privilege_settings">Привилегированное расширение</string>
|
||||
<string name="privilege_settings_hide_title">Скрытие настроек</string>
|
||||
<string name="privilege_settings_hide_description">Защита от обнаружения VPN для выбранных приложений</string>
|
||||
<string name="privilege_settings_hide_manage">Управление</string>
|
||||
<string name="privilege_settings_hide_test">Запустить тест</string>
|
||||
<string name="privilege_settings_hide_test_result">Результат теста</string>
|
||||
<string name="privilege_settings_hide_test_running">Выполняются тесты на обнаружение...</string>
|
||||
<string name="privilege_settings_hide_test_not_detected">Не обнаружено</string>
|
||||
<string name="privilege_settings_interface_rename_title">Переименование интерфейса</string>
|
||||
<string name="privilege_settings_interface_prefix">Префикс интерфейса</string>
|
||||
<string name="privilege_settings_vpn_detection_title">Обнаружение VPN</string>
|
||||
<string name="privilege_settings_view_logs">Просмотреть журнал</string>
|
||||
<string name="privilege_settings_export_debug">Экспорт отладочной информации</string>
|
||||
<string name="privilege_settings_hook_logs_empty">Журнал пуст</string>
|
||||
<string name="privilege_settings_export_debug_complete">Экспорт завершён</string>
|
||||
<string name="privilege_settings_export_debug_message">Файл содержит конфиденциальную информацию и не должен распространяться публично. Размер: %s</string>
|
||||
<string name="privilege_settings_export_debug_failed">Не удалось экспортировать отладочную информацию: %s</string>
|
||||
<string name="privilege_settings_risky_app_title">Предупреждение</string>
|
||||
<string name="privilege_settings_risky_vpn_message_single">Это приложение - VPN-клиент. Скрытие VPN может вызвать проблемы: %1$s</string>
|
||||
<string name="privilege_settings_risky_vpn_message_multi">Эти приложения - VPN-клиенты. Скрытие VPN может вызвать проблемы: %1$s</string>
|
||||
<string name="privilege_settings_risky_management_message_single">Это приложение имеет права управления сетью. Скрытие VPN может вызвать проблемы: %1$s</string>
|
||||
<string name="privilege_settings_risky_management_message_multi">Эти приложения имеют права управления сетью. Скрытие VPN может вызвать проблемы: %1$s</string>
|
||||
<string name="privilege_module_restart_action">Перезагрузить</string>
|
||||
<string name="privilege_module_restart_failed">Не удалось запросить перезагрузку: %1$s</string>
|
||||
<string name="privilege_module_restart_notification_title">Требуется перезагрузка</string>
|
||||
<string name="privilege_module_restart_notification_message">Модуль LSPosed обновлён. Перезагрузите устройство, чтобы применить изменения.</string>
|
||||
<string name="privilege_module_restart_channel">Модуль LSPosed</string>
|
||||
</resources>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<!-- App -->
|
||||
<string name="app_name" translatable="false">sing-box</string>
|
||||
<string name="app_version_title">应用版本</string>
|
||||
|
||||
<!-- Common Actions -->
|
||||
@@ -124,7 +125,6 @@
|
||||
<string name="no_profiles">没有配置的配置文件</string>
|
||||
<string name="add_profile">添加配置文件</string>
|
||||
<string name="update_profile">更新配置文件</string>
|
||||
<string name="share_profile">分享配置文件</string>
|
||||
<string name="import_remote_profile">导入远程配置</string>
|
||||
<string name="import_remote_profile_message">您确定要导入远程配置文件 %1$s 吗?您将连接到 %2$s 来下载配置。</string>
|
||||
<string name="import_from_file_description">从本地文件导入配置</string>
|
||||
@@ -134,9 +134,9 @@
|
||||
<string name="profile_name">名称</string>
|
||||
<string name="profile_type">类型</string>
|
||||
<string name="profile_source">源</string>
|
||||
<string name="profile_url" translatable="false">URL</string>
|
||||
<string name="profile_import_file">导入文件</string>
|
||||
<string name="profile_create">创建</string>
|
||||
<string name="profile_share">分享</string>
|
||||
<string name="profile_share_url">通过二维码分享 URL</string>
|
||||
<string name="profile_input_required">必须</string>
|
||||
<string name="profile_update">更新</string>
|
||||
@@ -165,7 +165,6 @@
|
||||
<string name="share_content_json">分享配置 JSON 文件</string>
|
||||
<string name="unsaved_changes">未保存的更改</string>
|
||||
<string name="unsaved_changes_message">您有未保存的更改。要放弃它们吗?</string>
|
||||
<string name="profile_qr_code_text">配置文件二维码:%s</string>
|
||||
<string name="import_profile_confirm_title">导入配置</string>
|
||||
<string name="import_profile_confirm_message">导入配置「%s」?</string>
|
||||
<string name="import_action">导入</string>
|
||||
@@ -266,6 +265,7 @@
|
||||
<string name="update_track_not_supported">当前轨道尚不支持检查更新</string>
|
||||
<string name="view_release">查看发布</string>
|
||||
<string name="downloading">下载中…</string>
|
||||
<string name="exporting">导出中…</string>
|
||||
<string name="no_updates_available">没有可用的更新</string>
|
||||
<string name="new_version_available">有新版本可用:%s</string>
|
||||
<string name="auto_update">自动更新</string>
|
||||
@@ -273,7 +273,6 @@
|
||||
|
||||
<!-- Silent Install -->
|
||||
<string name="silent_install">静默安装</string>
|
||||
<string name="silent_install_title">静默安装</string>
|
||||
<string name="silent_install_description">无需交互即可安装更新</string>
|
||||
<string name="silent_install_method">安装方式</string>
|
||||
<string name="silent_install_verify_failed">%s 不可用或权限被拒绝</string>
|
||||
@@ -335,8 +334,6 @@
|
||||
<string name="failed_save_profile">保存配置文件失败:%s</string>
|
||||
<string name="failed_save_logs">保存日志失败:%s</string>
|
||||
<string name="failed_share_logs">分享日志失败:%s</string>
|
||||
<string name="failed_save_qr_code">保存二维码失败:%s</string>
|
||||
<string name="failed_share_qr_code">分享二维码失败:%s</string>
|
||||
<string name="failed_read_configuration">读取配置失败:%s</string>
|
||||
|
||||
<!-- Success Messages -->
|
||||
@@ -344,7 +341,6 @@
|
||||
<string name="success_logs_saved">日志保存成功</string>
|
||||
<string name="success_configuration_saved">配置已保存</string>
|
||||
<string name="copied_to_clipboard">已复制到剪贴板</string>
|
||||
<string name="qr_code_saved_to_gallery">二维码已保存到相册</string>
|
||||
|
||||
<!-- Toast Messages -->
|
||||
<string name="toast_clipboard_empty">剪切板为空</string>
|
||||
@@ -376,16 +372,10 @@
|
||||
<string name="back_to_categories">返回分类</string>
|
||||
|
||||
<!-- QR Code -->
|
||||
<string name="intent_share_qr_code">分享二维码</string>
|
||||
|
||||
<!-- QR Stream (QRS) -->
|
||||
<string name="share_as_qrs">分享为 QRS</string>
|
||||
<string name="qrs_progress">接收中:%1$d / %2$d 块</string>
|
||||
<string name="qrs_speed">速度</string>
|
||||
<string name="qrs_interval_ms">间隔:%d 毫秒</string>
|
||||
<string name="qrs_scanning_mode">QRS 模式</string>
|
||||
<string name="qrs_fps">帧率</string>
|
||||
<string name="qrs_fps_interval">(%d 毫秒)</string>
|
||||
<string name="qrs_slice_size">分块大小</string>
|
||||
<string name="qrs_what_is_qrs">什么是 QRS</string>
|
||||
|
||||
@@ -403,6 +393,9 @@
|
||||
<string name="content_description_pause_logs">暂停日志</string>
|
||||
<string name="content_description_collapse_search">折叠搜索</string>
|
||||
<string name="content_description_search_logs">搜索日志</string>
|
||||
|
||||
<!-- Xposed Module -->
|
||||
<string name="xposed_description">sing-box 的特权增强</string>
|
||||
<!-- Privileged Enhancement -->
|
||||
<string name="privilege_settings">特权增强</string>
|
||||
<string name="privilege_module_title">特权模块</string>
|
||||
@@ -413,8 +406,6 @@
|
||||
<string name="privilege_settings_hide_test_result">测试结果</string>
|
||||
<string name="privilege_settings_hide_test_running">正在运行检测测试…</string>
|
||||
<string name="privilege_settings_hide_test_not_detected">未检测到</string>
|
||||
<string name="privilege_settings_test_interfaces">接口:</string>
|
||||
<string name="privilege_settings_test_http_proxy">HTTP 代理:</string>
|
||||
<string name="privilege_settings_interface_rename_title">接口重命名</string>
|
||||
<string name="privilege_settings_interface_prefix">接口前缀</string>
|
||||
<string name="privilege_settings_vpn_detection_title">VPN 检测</string>
|
||||
@@ -429,14 +420,11 @@
|
||||
<string name="privilege_settings_risky_vpn_message_multi">这些应用是 VPN 应用,抵抗 VPN 检测可能造成问题:%1$s</string>
|
||||
<string name="privilege_settings_risky_management_message_single">该应用具有网络管理权限,抵抗 VPN 检测可能造成问题:%1$s</string>
|
||||
<string name="privilege_settings_risky_management_message_multi">这些应用具有网络管理权限,抵抗 VPN 检测可能造成问题:%1$s</string>
|
||||
<string name="privilege_module_restart_title">需要重启</string>
|
||||
<string name="privilege_module_restart_message">LSPosed 模块已更新,请重启以应用改动。</string>
|
||||
<string name="privilege_module_restart_action">重新启动</string>
|
||||
<string name="privilege_module_restart_failed">请求重启失败:%1$s</string>
|
||||
<string name="privilege_module_restart_notification_title">需要重启</string>
|
||||
<string name="privilege_module_restart_notification_message">LSPosed 模块已更新,请重启以应用改动。</string>
|
||||
<string name="privilege_module_restart_channel">LSPosed 模块</string>
|
||||
<string name="lsposed_module">LSPosed 模块</string>
|
||||
<string name="lsposed_module_activated">LSPosed 模块已激活</string>
|
||||
<string name="lsposed_module_pending_update">LSPosed 模块待更新</string>
|
||||
<string name="lsposed_module_pending_downgrade">LSPosed 模块待降级</string>
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<!-- App -->
|
||||
<string name="app_name" translatable="false">sing-box</string>
|
||||
<string name="app_version_title">應用程式版本</string>
|
||||
|
||||
<!-- Common Actions -->
|
||||
<string name="ok">確定</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="save">儲存</string>
|
||||
<string name="discard">放棄</string>
|
||||
<string name="edit">編輯</string>
|
||||
<string name="close">關閉</string>
|
||||
<string name="dismiss">關閉</string>
|
||||
<string name="stop">停止</string>
|
||||
<string name="reset">重設</string>
|
||||
<string name="destroy">銷毀</string>
|
||||
<string name="clear">清除</string>
|
||||
<string name="browse">瀏覽</string>
|
||||
<string name="search">搜尋</string>
|
||||
<string name="action">操作</string>
|
||||
<string name="action_start">啟動</string>
|
||||
<string name="action_deselect">取消選擇</string>
|
||||
<string name="expand">展開</string>
|
||||
<string name="collapse">收合</string>
|
||||
<string name="expand_all">全部展開</string>
|
||||
<string name="collapse_all">全部收合</string>
|
||||
<string name="previous">上一個</string>
|
||||
<string name="next">下一個</string>
|
||||
<string name="update">更新</string>
|
||||
<string name="read_more">閱讀更多</string>
|
||||
<string name="no_thanks">不,謝謝</string>
|
||||
<string name="options">選項</string>
|
||||
<string name="more_options">更多選項</string>
|
||||
|
||||
<!-- Common States -->
|
||||
<string name="enabled">啟用</string>
|
||||
<string name="disabled">停用</string>
|
||||
<string name="loading">載入中...</string>
|
||||
<string name="calculating">計算中...</string>
|
||||
<string name="auto">自動</string>
|
||||
<string name="success">成功</string>
|
||||
<string name="default_text">預設</string>
|
||||
|
||||
<!-- Navigation Titles -->
|
||||
<string name="title_dashboard">儀表板</string>
|
||||
<string name="title_configuration">設定檔</string>
|
||||
<string name="title_log">日誌</string>
|
||||
<string name="title_settings">設定</string>
|
||||
<string name="title_app_settings">應用程式</string>
|
||||
<string name="title_new_profile">新增設定檔</string>
|
||||
<string name="title_edit_profile">編輯設定檔</string>
|
||||
<string name="title_edit_configuration">編輯設定</string>
|
||||
<string name="title_groups">群組</string>
|
||||
<string name="title_debug">偵錯</string>
|
||||
<string name="title_connections">連線</string>
|
||||
<string name="title_others">其他</string>
|
||||
<string name="title_scan_result">掃描結果</string>
|
||||
|
||||
<!-- Status -->
|
||||
<string name="status_default">服務未啟動</string>
|
||||
<string name="status_starting">啟動中</string>
|
||||
<string name="status_stopping">停止中</string>
|
||||
<string name="status_started">已啟動</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dashboard_items">儀表板項目</string>
|
||||
<string name="memory">記憶體</string>
|
||||
<string name="goroutines">協程</string>
|
||||
<string name="upload">上傳</string>
|
||||
<string name="download">下載</string>
|
||||
<string name="connections_in">入站</string>
|
||||
<string name="connections_out">出站</string>
|
||||
<string name="clash_mode">Clash 模式</string>
|
||||
<string name="mode">模式</string>
|
||||
<string name="system_proxy">系統代理</string>
|
||||
<string name="url_test">測試</string>
|
||||
<string name="drag_handle_to_reorder">拖曳控制柄以重新排序項目</string>
|
||||
<string name="drag_to_reorder">拖曳以重新排序</string>
|
||||
<string name="reset_order">重設順序</string>
|
||||
|
||||
<!-- Connections -->
|
||||
<string name="empty_connections">無連線</string>
|
||||
<string name="search_connections">搜尋連線…</string>
|
||||
<string name="close_connections_confirm">關閉所有連線?</string>
|
||||
<string name="connection_state_all">全部</string>
|
||||
<string name="connection_state_active">活躍</string>
|
||||
<string name="connection_state_closed">已關閉</string>
|
||||
<string name="connection_sort_date">日期</string>
|
||||
<string name="connection_sort_traffic">流量</string>
|
||||
<string name="connection_sort_traffic_total">總流量</string>
|
||||
<string name="connection_close">關閉</string>
|
||||
<string name="connection_close_all">全部關閉</string>
|
||||
<string name="connection_state">狀態</string>
|
||||
<string name="connection_details">連線詳情</string>
|
||||
<string name="connection_section_basic">基本資訊</string>
|
||||
<string name="connection_section_metadata">後設資料</string>
|
||||
<string name="connection_section_process">行程資訊</string>
|
||||
<string name="connection_created_at">建立時間</string>
|
||||
<string name="connection_closed_at">關閉時間</string>
|
||||
<string name="connection_duration">持續時間</string>
|
||||
<string name="connection_uplink">上行</string>
|
||||
<string name="connection_downlink">下行</string>
|
||||
<string name="connection_inbound">入站</string>
|
||||
<string name="connection_inbound_type">入站類型</string>
|
||||
<string name="connection_outbound">出站</string>
|
||||
<string name="connection_outbound_type">出站類型</string>
|
||||
<string name="connection_ip_version">IP 版本</string>
|
||||
<string name="connection_network">網路</string>
|
||||
<string name="connection_source">來源</string>
|
||||
<string name="connection_destination">目標</string>
|
||||
<string name="connection_domain">網域</string>
|
||||
<string name="connection_protocol">協定</string>
|
||||
<string name="connection_user">使用者</string>
|
||||
<string name="connection_from_outbound">來自出站</string>
|
||||
<string name="connection_match_rule">比對規則</string>
|
||||
<string name="connection_chain">鏈</string>
|
||||
<string name="connection_process_id">行程 ID</string>
|
||||
<string name="connection_user_id">使用者 ID</string>
|
||||
<string name="connection_user_name">使用者名稱</string>
|
||||
<string name="connection_process_path">行程路徑</string>
|
||||
<string name="connection_package_name">套件名稱</string>
|
||||
|
||||
<!-- Profiles -->
|
||||
<string name="no_profiles">尚未設定任何設定檔</string>
|
||||
<string name="add_profile">新增設定檔</string>
|
||||
<string name="update_profile">更新設定檔</string>
|
||||
<string name="import_remote_profile">匯入遠端設定</string>
|
||||
<string name="import_remote_profile_message">您確定要匯入遠端設定檔 %1$s 嗎?您將連線到 %2$s 以下載設定。</string>
|
||||
<string name="import_from_file_description">從本機檔案匯入設定</string>
|
||||
<string name="scan_qr_code_description">掃描設定 QR 碼</string>
|
||||
<string name="create_new_profile_description">從零建立新的設定檔</string>
|
||||
<string name="icloud_profile_unsupported">目前平台不支援 iCloud 設定檔</string>
|
||||
<string name="profile_name">名稱</string>
|
||||
<string name="profile_type">類型</string>
|
||||
<string name="profile_source">來源</string>
|
||||
<string name="profile_url" translatable="false">URL</string>
|
||||
<string name="profile_import_file">匯入檔案</string>
|
||||
<string name="profile_create">建立</string>
|
||||
<string name="profile_share_url">透過二維碼分享 URL</string>
|
||||
<string name="profile_input_required">必填</string>
|
||||
<string name="profile_update">更新</string>
|
||||
<string name="profile_auto_update">自動更新</string>
|
||||
<string name="profile_auto_update_interval">自動更新間隔 (分)</string>
|
||||
<string name="profile_auto_update_interval_minimum_hint">最低值為 15</string>
|
||||
<string name="profile_type_local">本地</string>
|
||||
<string name="profile_type_remote">遠端</string>
|
||||
<string name="profile_type_remote_updated">遠端 • %s</string>
|
||||
<string name="profile_source_create_new">建立新項目</string>
|
||||
<string name="profile_source_import">匯入</string>
|
||||
<string name="profile_add_import_file">從檔案匯入</string>
|
||||
<string name="profile_add_scan_qr_code">掃描二維碼</string>
|
||||
<string name="profile_add_create_manually">手動建立</string>
|
||||
<string name="profile_override">設定覆寫</string>
|
||||
<string name="basic_information">基本資訊</string>
|
||||
<string name="remote_configuration">遠端設定</string>
|
||||
<string name="last_updated_format">最後更新:%s</string>
|
||||
<string name="content">內容</string>
|
||||
<string name="json_viewer">JSON 檢視器</string>
|
||||
<string name="json_editor">JSON 編輯器</string>
|
||||
<string name="view_configuration">檢視設定</string>
|
||||
<string name="save_as_file">儲存為檔案</string>
|
||||
<string name="share_as_file">分享為檔案</string>
|
||||
<string name="save_content_json">儲存設定 JSON 檔案</string>
|
||||
<string name="share_content_json">分享設定 JSON 檔案</string>
|
||||
<string name="unsaved_changes">未儲存的更改</string>
|
||||
<string name="unsaved_changes_message">您有未儲存的更改。要放棄它們嗎?</string>
|
||||
<string name="import_profile_confirm_title">匯入設定檔</string>
|
||||
<string name="import_profile_confirm_message">匯入設定檔「%s」?</string>
|
||||
<string name="import_action">匯入</string>
|
||||
|
||||
<!-- Groups -->
|
||||
<string name="group_selected_title">已選取</string>
|
||||
|
||||
<!-- Log -->
|
||||
<string name="log_level">日誌等級</string>
|
||||
<string name="filter_label">篩選器:%s</string>
|
||||
<string name="clear_filter">清除</string>
|
||||
<string name="clear_logs">清除日誌</string>
|
||||
<string name="search_logs_placeholder">搜尋日誌…</string>
|
||||
<string name="logs_copied_to_clipboard">日誌已複製到剪貼簿</string>
|
||||
<string name="no_logs_to_copy">沒有日誌可複製</string>
|
||||
<string name="no_logs_to_share">沒有日誌可分享</string>
|
||||
<string name="intent_share_logs">分享日誌</string>
|
||||
<string name="selected_count">已選擇 %d 項</string>
|
||||
<string name="not_selected">未選擇</string>
|
||||
<string name="save_to_clipboard">複製到剪貼簿</string>
|
||||
<string name="save_to_file">儲存到檔案</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="service">服務</string>
|
||||
<string name="core">核心</string>
|
||||
<string name="core_version_title">核心版本</string>
|
||||
<string name="core_data_size">資料大小</string>
|
||||
<string name="about">關於</string>
|
||||
<string name="source_code">原始碼</string>
|
||||
<string name="sponsor">贊助</string>
|
||||
<string name="working_directory">工作目錄</string>
|
||||
<string name="disable_deprecated_warnings">停用過時警告</string>
|
||||
<string name="ignore_memory_limit">忽略記憶體限制</string>
|
||||
<string name="ignore_memory_limit_description">不對 sing-box 強制執行記憶體限制。</string>
|
||||
<string name="auto_redirect">自動重定向</string>
|
||||
<string name="auto_redirect_description">需要 ROOT 權限</string>
|
||||
<string name="system_http_proxy">系統 HTTP 代理</string>
|
||||
<string name="update_settings">更新設定</string>
|
||||
|
||||
<!-- Per-App Proxy -->
|
||||
<string name="per_app_proxy">每個應用程式代理</string>
|
||||
<string name="per_app_proxy_mode">代理模式</string>
|
||||
<string name="per_app_proxy_mode_include">包含</string>
|
||||
<string name="per_app_proxy_mode_include_description">僅允許選定的應用程式透過 VPN</string>
|
||||
<string name="per_app_proxy_mode_exclude">排除</string>
|
||||
<string name="per_app_proxy_mode_exclude_description">選定的應用將從 VPN 中排除</string>
|
||||
<string name="per_app_proxy_action_copy">複製</string>
|
||||
<string name="per_app_proxy_action_copy_package_name">套件名稱</string>
|
||||
<string name="per_app_proxy_action_copy_uid">UID</string>
|
||||
<string name="per_app_proxy_sort_mode">排序</string>
|
||||
<string name="per_app_proxy_sort_mode_name">依名稱</string>
|
||||
<string name="per_app_proxy_sort_mode_package_name">依套件名稱</string>
|
||||
<string name="per_app_proxy_sort_mode_uid">依 UID</string>
|
||||
<string name="per_app_proxy_sort_mode_install_time">依安裝時間</string>
|
||||
<string name="per_app_proxy_sort_mode_update_time">依更新時間</string>
|
||||
<string name="per_app_proxy_sort_mode_reverse">反向排序</string>
|
||||
<string name="per_app_proxy_filter">篩選</string>
|
||||
<string name="per_app_proxy_hide_system_apps">隱藏系統應用程式</string>
|
||||
<string name="per_app_proxy_hide_offline_apps">隱藏離線應用程式</string>
|
||||
<string name="per_app_proxy_hide_disabled_apps">隱藏停用應用程式</string>
|
||||
<string name="per_app_proxy_select">選擇</string>
|
||||
<string name="per_app_proxy_select_all">全選</string>
|
||||
<string name="per_app_proxy_select_none">全部取消選取</string>
|
||||
<string name="per_app_proxy_backup">備份</string>
|
||||
<string name="per_app_proxy_import">從剪貼簿匯入</string>
|
||||
<string name="per_app_proxy_export">匯出到剪貼簿</string>
|
||||
<string name="per_app_proxy_scan">掃描</string>
|
||||
<string name="per_app_proxy_scan_china_apps">中國應用程式</string>
|
||||
<string name="per_app_proxy_manage">管理</string>
|
||||
<string name="content_description_app_icon">應用程式圖示</string>
|
||||
<string name="per_app_proxy_managed_mode">受管理模式</string>
|
||||
<string name="per_app_proxy_managed_mode_description">自動排除中國應用程式</string>
|
||||
<string name="per_app_proxy_package_query_mode">模式</string>
|
||||
<string name="per_app_proxy_shizuku_required">透過 Play 商店安裝時,每個應用程式代理需要 Shizuku。Google Play 拒絕允許我們使用 QUERY_ALL_PACKAGES 權限(同時不禁止其他類似應用程式這樣做),而這是列出應用程式清單所必需的。</string>
|
||||
<string name="per_app_proxy_root_required">透過 Play 商店安裝時,每個應用程式代理需要 ROOT。Google Play 拒絕允許我們使用 QUERY_ALL_PACKAGES 權限(同時不禁止其他類似應用程式這樣做),而這是列出應用程式清單所必需的。</string>
|
||||
<string name="privileged_access_required">需要 Root 或 Shizuku 權限來取得完整應用程式清單</string>
|
||||
|
||||
<!-- Permissions -->
|
||||
<string name="background_permission">背景執行權限</string>
|
||||
<string name="background_permission_description">請授予必要權限,讓 VPN 可以正常運作。\n\n若您使用的是中國廠商裝置,授權後此提示卡片可能仍會顯示。</string>
|
||||
<string name="request_background_permission">忽略電池最佳化</string>
|
||||
<string name="location_permission_title">位置權限</string>
|
||||
<string name="location_permission_description">您的設定檔包含 <strong><tt>wifi_ssid</tt> 或 <tt>wifi_bssid</tt> 路由規則</strong>。為了使它們正常工作,sing-box 在<strong>背景</strong>使用 <strong>位置</strong> 權限來取得已連線 Wi-Fi 網路資訊。這些資訊將<strong>僅用於路由</strong>。</string>
|
||||
<string name="location_permission_background_description">在 Android 10 及更高版本中,需要<strong>背景位置</strong>權限。選擇<strong>始終允許</strong>以授予權限。</string>
|
||||
<string name="notification_permission_title">通知權限</string>
|
||||
<string name="notification_permission_required_description">若未授予通知權限,sing-box 無法顯示即時網速。請先授權,或關閉即時網速通知後再啟動服務。</string>
|
||||
<string name="root_access_required">需要 Root 權限</string>
|
||||
<string name="root_access_denied">Root 權限被拒絕</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="check_update">檢查更新</string>
|
||||
<string name="check_update_automatic">自動檢查更新</string>
|
||||
<string name="check_update_prompt_play">是否啟用從 **Play Store** 自動檢查更新?</string>
|
||||
<string name="check_update_prompt_github">是否啟用從 **GitHub** 自動檢查更新?</string>
|
||||
<string name="update_track">更新通道</string>
|
||||
<string name="update_track_stable">穩定版</string>
|
||||
<string name="update_track_beta">測試版</string>
|
||||
<string name="update_track_not_supported">目前通道尚不支援檢查更新</string>
|
||||
<string name="view_release">查看發布</string>
|
||||
<string name="downloading">下載中…</string>
|
||||
<string name="exporting">匯出中…</string>
|
||||
<string name="no_updates_available">沒有可用的更新</string>
|
||||
<string name="new_version_available">有新版本可用:%s</string>
|
||||
<string name="auto_update">自動更新</string>
|
||||
<string name="auto_update_description">在背景自動下載並安裝更新</string>
|
||||
|
||||
<!-- Silent Install -->
|
||||
<string name="silent_install">靜默安裝</string>
|
||||
<string name="silent_install_description">無需互動即可安裝更新</string>
|
||||
<string name="silent_install_method">安裝方式</string>
|
||||
<string name="silent_install_verify_failed">%s 不可用或權限被拒絕</string>
|
||||
<string name="install_method_package_installer">PackageInstaller</string>
|
||||
<string name="install_method_shizuku">Shizuku</string>
|
||||
<string name="install_method_root">ROOT</string>
|
||||
<string name="package_installer_not_available">未授予安裝權限</string>
|
||||
<string name="grant_install_permission">授予安裝權限</string>
|
||||
<string name="grant_install_permission_description">允許從此來源安裝應用程式</string>
|
||||
|
||||
<!-- Shizuku -->
|
||||
<string name="shizuku_not_available">Shizuku 未安裝或未執行</string>
|
||||
<string name="shizuku_description">Shizuku 允許應用程式以更高權限直接使用系統 API</string>
|
||||
<string name="get_shizuku">獲取 Shizuku</string>
|
||||
<string name="start_shizuku">啟動 Shizuku</string>
|
||||
<string name="request_shizuku">授權 Shizuku</string>
|
||||
|
||||
<!-- Time -->
|
||||
<string name="time_just_now">剛剛</string>
|
||||
<string name="time_yesterday">昨天</string>
|
||||
<string name="time_now">現在</string>
|
||||
<string name="time_yesterday_short">1天</string>
|
||||
<string name="time_minutes_short">%d分</string>
|
||||
<string name="time_hours_short">%d時</string>
|
||||
<string name="time_days_short">%d天</string>
|
||||
<plurals name="time_minutes_ago">
|
||||
<item quantity="one">%d 分鐘前</item>
|
||||
<item quantity="other">%d 分鐘前</item>
|
||||
</plurals>
|
||||
<plurals name="time_hours_ago">
|
||||
<item quantity="one">%d 小時前</item>
|
||||
<item quantity="other">%d 小時前</item>
|
||||
</plurals>
|
||||
<plurals name="time_days_ago">
|
||||
<item quantity="one">%d 天前</item>
|
||||
<item quantity="other">%d 天前</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Menu -->
|
||||
<string name="menu_undo">撤銷</string>
|
||||
<string name="menu_redo">重做</string>
|
||||
<string name="menu_format">格式化</string>
|
||||
<string name="menu_delete">刪除</string>
|
||||
<string name="menu_share">分享</string>
|
||||
|
||||
<!-- Errors -->
|
||||
<string name="error_title">錯誤</string>
|
||||
<string name="error_missing_vpn_permission">缺少 VPN 權限</string>
|
||||
<string name="error_empty_configuration">設定為空</string>
|
||||
<string name="error_empty_file">空檔案</string>
|
||||
<string name="error_decode_profile">解碼設定檔失敗:%s</string>
|
||||
<string name="error_invalid_configuration">無效的 sing-box 設定:%s</string>
|
||||
<string name="error_start_command_server">啟動命令伺服器</string>
|
||||
<string name="error_create_service">建立服務</string>
|
||||
<string name="error_start_service">啟動服務</string>
|
||||
<string name="error_deprecated_warning">棄用警告</string>
|
||||
<string name="error_deprecated_documentation">文件</string>
|
||||
<string name="file_manager_missing">您的裝置缺少 Android 標準檔案選擇器,請安裝一個,例如 Material Files。</string>
|
||||
<string name="no_file_manager">未找到檔案管理器</string>
|
||||
|
||||
<!-- Failed Operations -->
|
||||
<string name="failed_save_profile">儲存設定檔失敗:%s</string>
|
||||
<string name="failed_save_logs">儲存日誌失敗:%s</string>
|
||||
<string name="failed_share_logs">分享日誌失敗:%s</string>
|
||||
<string name="failed_read_configuration">讀取設定失敗:%s</string>
|
||||
|
||||
<!-- Success Messages -->
|
||||
<string name="success_profile_saved">設定檔儲存成功</string>
|
||||
<string name="success_logs_saved">日誌儲存成功</string>
|
||||
<string name="success_configuration_saved">設定已儲存</string>
|
||||
<string name="copied_to_clipboard">已複製到剪貼簿</string>
|
||||
|
||||
<!-- Toast Messages -->
|
||||
<string name="toast_clipboard_empty">剪貼簿為空</string>
|
||||
<string name="toast_copied_to_clipboard">已匯出到剪貼簿</string>
|
||||
<string name="toast_imported_from_clipboard">已從剪貼簿匯入</string>
|
||||
|
||||
<!-- Scanning -->
|
||||
<string name="profile_add_scan_use_front_camera">前鏡頭</string>
|
||||
<string name="profile_add_scan_use_vendor_analyzer">使用 MLKit 掃描</string>
|
||||
<string name="profile_add_scan_enable_torch">手電筒</string>
|
||||
<string name="message_scanning">掃描中...</string>
|
||||
<string name="message_scan_app_no_apps_found">找不到符合條件的應用程式</string>
|
||||
<string name="message_scan_app_found">找到以下應用程式,請選擇您想要的操作。</string>
|
||||
|
||||
<!-- Icon Selection -->
|
||||
<string name="icon">圖示</string>
|
||||
<string name="icon_count_format">%d 個圖示</string>
|
||||
<string name="no_icons_found">未找到圖示</string>
|
||||
<string name="no_icons_match">沒有符合 \"%s\" 的圖示</string>
|
||||
<string name="profile_icon">設定檔圖示</string>
|
||||
<string name="select_icon">選擇圖示</string>
|
||||
<string name="select_profile_icon">選擇設定檔圖示</string>
|
||||
<string name="search_icons_placeholder">搜尋圖示...</string>
|
||||
<string name="search_icons">搜尋圖示</string>
|
||||
<string name="close_search">關閉搜尋</string>
|
||||
<string name="current_icon_format">當前:%s</string>
|
||||
<string name="categories">分類</string>
|
||||
<string name="all_icons">所有圖示</string>
|
||||
<string name="back_to_categories">回到分類</string>
|
||||
|
||||
<!-- QR Code -->
|
||||
|
||||
<!-- QR Stream (QRS) -->
|
||||
<string name="share_as_qrs">分享為 QRS</string>
|
||||
<string name="qrs_fps">FPS</string>
|
||||
<string name="qrs_slice_size">分片大小</string>
|
||||
<string name="qrs_what_is_qrs">什麼是 QRS</string>
|
||||
|
||||
<!-- Search -->
|
||||
<string name="search_placeholder">在文件中尋找</string>
|
||||
|
||||
<!-- Content Descriptions (Accessibility) -->
|
||||
<string name="content_description_back">返回</string>
|
||||
<string name="content_description_scroll_to_bottom">滾動到底部</string>
|
||||
<string name="content_description_exit_selection_mode">離開選取模式</string>
|
||||
<string name="content_description_copy_selected">複製已選取項目</string>
|
||||
<string name="content_description_clear_search">清除搜尋</string>
|
||||
<string name="content_description_qr_code">QR 碼</string>
|
||||
<string name="content_description_resume_logs">繼續顯示日誌</string>
|
||||
<string name="content_description_pause_logs">暫停日誌</string>
|
||||
<string name="content_description_collapse_search">收合搜尋</string>
|
||||
<string name="content_description_search_logs">搜尋日誌</string>
|
||||
|
||||
<!-- Xposed Module -->
|
||||
<string name="xposed_description">sing-box 的特權強化</string>
|
||||
<!-- Privileged Enhancement -->
|
||||
<string name="privilege_settings">特權強化</string>
|
||||
<string name="privilege_module_title">特權模組</string>
|
||||
<string name="privilege_settings_hide_title">隱藏設定</string>
|
||||
<string name="privilege_settings_hide_description">為已選取的應用程式啟用 VPN 偵測防護</string>
|
||||
<string name="privilege_settings_hide_manage">管理</string>
|
||||
<string name="privilege_settings_hide_test">執行測試</string>
|
||||
<string name="privilege_settings_hide_test_result">測試結果</string>
|
||||
<string name="privilege_settings_hide_test_running">正在執行檢測測試…</string>
|
||||
<string name="privilege_settings_hide_test_not_detected">未檢測到</string>
|
||||
<string name="privilege_settings_interface_rename_title">介面重新命名</string>
|
||||
<string name="privilege_settings_interface_prefix">介面前綴</string>
|
||||
<string name="privilege_settings_vpn_detection_title">VPN 檢測</string>
|
||||
<string name="privilege_settings_view_logs">檢視日誌</string>
|
||||
<string name="privilege_settings_export_debug">匯出除錯資訊</string>
|
||||
<string name="privilege_settings_hook_logs_empty">暫無日誌</string>
|
||||
<string name="privilege_settings_export_debug_complete">匯出完成</string>
|
||||
<string name="privilege_settings_export_debug_message">此檔案包含隱私資訊,不應公開分享。大小:%s</string>
|
||||
<string name="privilege_settings_export_debug_failed">匯出除錯資訊失敗:%s</string>
|
||||
<string name="privilege_settings_risky_app_title">警告</string>
|
||||
<string name="privilege_settings_risky_vpn_message_single">此應用程式是 VPN 應用程式,抵抗 VPN 檢測可能造成問題:%1$s</string>
|
||||
<string name="privilege_settings_risky_vpn_message_multi">這些應用程式是 VPN 應用程式,抵抗 VPN 檢測可能造成問題:%1$s</string>
|
||||
<string name="privilege_settings_risky_management_message_single">此應用程式具有網路管理權限,抵抗 VPN 檢測可能造成問題:%1$s</string>
|
||||
<string name="privilege_settings_risky_management_message_multi">這些應用程式具有網路管理權限,抵抗 VPN 檢測可能造成問題:%1$s</string>
|
||||
<string name="privilege_module_restart_action">重新啟動</string>
|
||||
<string name="privilege_module_restart_failed">請求重啟失敗:%1$s</string>
|
||||
<string name="privilege_module_restart_notification_title">需要重啟</string>
|
||||
<string name="privilege_module_restart_notification_message">LSPosed 模組已更新,請重啟以套用變更。</string>
|
||||
<string name="privilege_module_restart_channel">LSPosed 模組</string>
|
||||
<string name="lsposed_module_activated">LSPosed 模組已啟用</string>
|
||||
<string name="lsposed_module_pending_update">LSPosed 模組待更新</string>
|
||||
<string name="lsposed_module_pending_downgrade">LSPosed 模組待降級</string>
|
||||
<string name="lsposed_module_not_activated">LSPosed 模組未啟用</string>
|
||||
</resources>
|
||||
@@ -125,7 +125,6 @@
|
||||
<string name="no_profiles">No profiles configured</string>
|
||||
<string name="add_profile">Add Profile</string>
|
||||
<string name="update_profile">Update profile</string>
|
||||
<string name="share_profile">Share Profile</string>
|
||||
<string name="import_remote_profile">Import remote profile</string>
|
||||
<string name="import_remote_profile_message">Are you sure to import remote profile %1$s? You will connect to %2$s to download the configuration.</string>
|
||||
<string name="import_from_file_description">Import configuration from a local file</string>
|
||||
@@ -138,7 +137,6 @@
|
||||
<string name="profile_url" translatable="false">URL</string>
|
||||
<string name="profile_import_file">Import File</string>
|
||||
<string name="profile_create">Create</string>
|
||||
<string name="profile_share">Share</string>
|
||||
<string name="profile_share_url">Share URL as QR Code</string>
|
||||
<string name="profile_input_required">Required</string>
|
||||
<string name="profile_update">Update</string>
|
||||
@@ -167,7 +165,6 @@
|
||||
<string name="share_content_json">Share Content JSON File</string>
|
||||
<string name="unsaved_changes">Unsaved Changes</string>
|
||||
<string name="unsaved_changes_message">You have unsaved changes. Do you want to discard them?</string>
|
||||
<string name="profile_qr_code_text">Profile QR Code: %s</string>
|
||||
<string name="import_profile_confirm_title">Import Profile</string>
|
||||
<string name="import_profile_confirm_message">Import profile \"%s\"?</string>
|
||||
<string name="import_action">Import</string>
|
||||
@@ -276,7 +273,6 @@
|
||||
|
||||
<!-- Silent Install -->
|
||||
<string name="silent_install">Silent Install</string>
|
||||
<string name="silent_install_title">Silent Install</string>
|
||||
<string name="silent_install_description">Install updates without interaction</string>
|
||||
<string name="silent_install_method">Install Method</string>
|
||||
<string name="silent_install_verify_failed">%s is not available or permission denied</string>
|
||||
@@ -341,8 +337,6 @@
|
||||
<string name="failed_save_profile">Failed to save profile: %s</string>
|
||||
<string name="failed_save_logs">Failed to save logs: %s</string>
|
||||
<string name="failed_share_logs">Failed to share logs: %s</string>
|
||||
<string name="failed_save_qr_code">Failed to save QR code: %s</string>
|
||||
<string name="failed_share_qr_code">Failed to share QR code: %s</string>
|
||||
<string name="failed_read_configuration">Failed to read configuration: %s</string>
|
||||
|
||||
<!-- Success Messages -->
|
||||
@@ -350,7 +344,6 @@
|
||||
<string name="success_logs_saved">Logs saved successfully</string>
|
||||
<string name="success_configuration_saved">Configuration saved</string>
|
||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||
<string name="qr_code_saved_to_gallery">QR code saved to gallery</string>
|
||||
|
||||
<!-- Toast Messages -->
|
||||
<string name="toast_clipboard_empty">Clipboard is empty</string>
|
||||
@@ -382,16 +375,10 @@
|
||||
<string name="back_to_categories">Back to categories</string>
|
||||
|
||||
<!-- QR Code -->
|
||||
<string name="intent_share_qr_code">Share QR Code</string>
|
||||
|
||||
<!-- QR Stream (QRS) -->
|
||||
<string name="share_as_qrs">Share as QR Stream</string>
|
||||
<string name="qrs_progress">Receiving: %1$d / %2$d blocks</string>
|
||||
<string name="qrs_speed">Speed</string>
|
||||
<string name="qrs_interval_ms">Interval: %d ms</string>
|
||||
<string name="qrs_scanning_mode">QR Stream Mode</string>
|
||||
<string name="qrs_fps">FPS</string>
|
||||
<string name="qrs_fps_interval">(%d ms)</string>
|
||||
<string name="qrs_slice_size">Slice Size</string>
|
||||
<string name="qrs_what_is_qrs">What is QRS</string>
|
||||
|
||||
@@ -415,12 +402,10 @@
|
||||
|
||||
<!-- Privileged Enhancement -->
|
||||
<string name="privilege_module_title">Privilege modules</string>
|
||||
<string name="lsposed_module">LSPosed Module</string>
|
||||
<string name="lsposed_module_activated">LSPosed module activated</string>
|
||||
<string name="lsposed_module_pending_update">LSPosed module update pending</string>
|
||||
<string name="lsposed_module_pending_downgrade">LSPosed module downgrade pending</string>
|
||||
<string name="lsposed_module_not_activated">LSPosed module not activated</string>
|
||||
<string name="lsposed_module_description">System Framework only.</string>
|
||||
<string name="privilege_settings">Privileged Enhancement</string>
|
||||
<string name="privilege_settings_hide_title">Hide Settings</string>
|
||||
<string name="privilege_settings_hide_description">Resist VPN detection for selected apps</string>
|
||||
@@ -429,8 +414,6 @@
|
||||
<string name="privilege_settings_hide_test_result">Test Result</string>
|
||||
<string name="privilege_settings_hide_test_running">Running detection tests…</string>
|
||||
<string name="privilege_settings_hide_test_not_detected">Not detected</string>
|
||||
<string name="privilege_settings_test_interfaces">Interfaces:</string>
|
||||
<string name="privilege_settings_test_http_proxy">HTTP proxy:</string>
|
||||
<string name="privilege_settings_interface_rename_title">Interface Rename</string>
|
||||
<string name="privilege_settings_interface_prefix">Interface prefix</string>
|
||||
<string name="privilege_settings_vpn_detection_title">VPN Detection</string>
|
||||
@@ -445,12 +428,9 @@
|
||||
<string name="privilege_settings_risky_vpn_message_multi">These apps are VPN apps. Resisting VPN detection may cause issues: %1$s</string>
|
||||
<string name="privilege_settings_risky_management_message_single">This app has network management permissions. Resisting VPN detection may cause issues: %1$s</string>
|
||||
<string name="privilege_settings_risky_management_message_multi">These apps have network management permissions. Resisting VPN detection may cause issues: %1$s</string>
|
||||
<string name="privilege_module_restart_title">Reboot required</string>
|
||||
<string name="privilege_module_restart_message">LSPosed module updated. Reboot to apply changes.</string>
|
||||
<string name="privilege_module_restart_action">Restart</string>
|
||||
<string name="privilege_module_restart_failed">Failed to request reboot: %1$s</string>
|
||||
<string name="privilege_module_restart_notification_title">Reboot required</string>
|
||||
<string name="privilege_module_restart_notification_message">LSPosed module updated. Reboot to apply changes.</string>
|
||||
<string name="privilege_module_restart_channel">LSPosed Module</string>
|
||||
<string name="reboot_required">Reboot required</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
VERSION_CODE=617
|
||||
VERSION_NAME=1.13.0-rc.2
|
||||
VERSION_CODE=619
|
||||
VERSION_NAME=1.13.0-rc.3
|
||||
GO_VERSION=go1.25.7
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
)
|
||||
|
||||
var target string
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&target, "target", "android", "target platform (android or apple)")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
args := []string{
|
||||
"generate",
|
||||
"-v",
|
||||
"--config", "experimental/libbox/ffi.json",
|
||||
"--platform-type", target,
|
||||
}
|
||||
command := exec.Command("sing-ffi", args...)
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
err := command.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
copyArtifacts(target)
|
||||
}
|
||||
|
||||
func copyArtifacts(target string) {
|
||||
switch target {
|
||||
case "android":
|
||||
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
|
||||
if rw.IsDir(copyPath) {
|
||||
copyPath, _ = filepath.Abs(copyPath)
|
||||
for _, name := range []string{"libbox.aar", "libbox-legacy.aar"} {
|
||||
artifactPath, found := findArtifactPath(name)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
targetPath := filepath.Join(target, artifactPath)
|
||||
os.RemoveAll(targetPath)
|
||||
err := os.Rename(artifactPath, targetPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Info("copied ", name, " to ", copyPath)
|
||||
}
|
||||
}
|
||||
case "apple":
|
||||
copyPath := filepath.Join("..", "sing-box-for-apple")
|
||||
if rw.IsDir(copyPath) {
|
||||
sourceDir, found := findArtifactPath("Libbox.xcframework")
|
||||
if !found {
|
||||
log.Fatal("Libbox.xcframework not found in current directory or experimental/libbox")
|
||||
}
|
||||
|
||||
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
|
||||
targetDir, _ = filepath.Abs(targetDir)
|
||||
err := os.RemoveAll(targetDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.Rename(sourceDir, targetDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Info("copied ", sourceDir, " to ", targetDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findArtifactPath(name string) (string, bool) {
|
||||
candidates := []string{
|
||||
name,
|
||||
filepath.Join("experimental", "libbox", name),
|
||||
}
|
||||
for _, candidate := range candidates {
|
||||
if rw.IsFile(candidate) || rw.IsDir(candidate) {
|
||||
return candidate, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
@@ -103,6 +104,7 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
|
||||
i.clashServer = service.FromContext[adapter.ClashServer](ctx)
|
||||
i.pauseManager = service.FromContext[pause.Manager](ctx)
|
||||
i.cacheFile = service.FromContext[adapter.CacheFile](ctx)
|
||||
log.SetStdLogger(boxInstance.LogFactory().Logger())
|
||||
return i, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.13.0-rc.2
|
||||
#### 1.13.0-rc.3
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -169,6 +169,14 @@ Also, documentation has been updated with a warning about uTLS fingerprinting vu
|
||||
uTLS is not recommended for censorship circumvention due to fundamental architectural limitations;
|
||||
use NaiveProxy instead for TLS fingerprint resistance.
|
||||
|
||||
#### 1.12.21
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.13.0-rc.2
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.20
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -362,7 +362,7 @@ func (c *CommandClient) handleStatusStream() {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteStatus(StatusMessageFromGRPC(status))
|
||||
c.handler.WriteStatus(statusMessageFromGRPC(status))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ func (c *CommandClient) handleGroupStream() {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteGroups(OutboundGroupIteratorFromGRPC(groups))
|
||||
c.handler.WriteGroups(outboundGroupIteratorFromGRPC(groups))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@ func (c *CommandClient) handleConnectionsStream() {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
libboxEvents := ConnectionEventsFromGRPC(events)
|
||||
libboxEvents := connectionEventsFromGRPC(events)
|
||||
c.handler.WriteConnectionEvents(libboxEvents)
|
||||
}
|
||||
}
|
||||
@@ -523,7 +523,7 @@ func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return SystemProxyStatusFromGRPC(status), nil
|
||||
return systemProxyStatusFromGRPC(status), nil
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ type CommandServerHandler interface {
|
||||
}
|
||||
|
||||
func NewCommandServer(handler CommandServerHandler, platformInterface PlatformInterface) (*CommandServer, error) {
|
||||
ctx := BaseContext(platformInterface)
|
||||
ctx := baseContext(platformInterface)
|
||||
platformWrapper := &platformInterfaceWrapper{
|
||||
iif: platformInterface,
|
||||
useProcFS: platformInterface.UseProcFS(),
|
||||
|
||||
@@ -32,11 +32,11 @@ type OutboundGroup struct {
|
||||
Selectable bool
|
||||
Selected string
|
||||
IsExpand bool
|
||||
ItemList []*OutboundGroupItem
|
||||
itemList []*OutboundGroupItem
|
||||
}
|
||||
|
||||
func (g *OutboundGroup) GetItems() OutboundGroupItemIterator {
|
||||
return newIterator(g.ItemList)
|
||||
return newIterator(g.itemList)
|
||||
}
|
||||
|
||||
type OutboundGroupIterator interface {
|
||||
@@ -267,12 +267,12 @@ type Connection struct {
|
||||
Rule string
|
||||
Outbound string
|
||||
OutboundType string
|
||||
ChainList []string
|
||||
chainList []string
|
||||
ProcessInfo *ProcessInfo
|
||||
}
|
||||
|
||||
func (c *Connection) Chain() StringIterator {
|
||||
return newIterator(c.ChainList)
|
||||
return newIterator(c.chainList)
|
||||
}
|
||||
|
||||
func (c *Connection) DisplayDestination() string {
|
||||
@@ -292,7 +292,7 @@ type ConnectionIterator interface {
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
func StatusMessageFromGRPC(status *daemon.Status) *StatusMessage {
|
||||
func statusMessageFromGRPC(status *daemon.Status) *StatusMessage {
|
||||
if status == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -309,7 +309,7 @@ func StatusMessageFromGRPC(status *daemon.Status) *StatusMessage {
|
||||
}
|
||||
}
|
||||
|
||||
func OutboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator {
|
||||
func outboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator {
|
||||
if groups == nil || len(groups.Group) == 0 {
|
||||
return newIterator([]*OutboundGroup{})
|
||||
}
|
||||
@@ -323,7 +323,7 @@ func OutboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator
|
||||
IsExpand: g.IsExpand,
|
||||
}
|
||||
for _, item := range g.Items {
|
||||
libboxGroup.ItemList = append(libboxGroup.ItemList, &OutboundGroupItem{
|
||||
libboxGroup.itemList = append(libboxGroup.itemList, &OutboundGroupItem{
|
||||
Tag: item.Tag,
|
||||
Type: item.Type,
|
||||
URLTestTime: item.UrlTestTime,
|
||||
@@ -335,7 +335,7 @@ func OutboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator
|
||||
return newIterator(libboxGroups)
|
||||
}
|
||||
|
||||
func ConnectionFromGRPC(conn *daemon.Connection) Connection {
|
||||
func connectionFromGRPC(conn *daemon.Connection) Connection {
|
||||
var processInfo *ProcessInfo
|
||||
if conn.ProcessInfo != nil {
|
||||
processInfo = &ProcessInfo{
|
||||
@@ -367,12 +367,12 @@ func ConnectionFromGRPC(conn *daemon.Connection) Connection {
|
||||
Rule: conn.Rule,
|
||||
Outbound: conn.Outbound,
|
||||
OutboundType: conn.OutboundType,
|
||||
ChainList: conn.ChainList,
|
||||
chainList: conn.ChainList,
|
||||
ProcessInfo: processInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func ConnectionEventFromGRPC(event *daemon.ConnectionEvent) *ConnectionEvent {
|
||||
func connectionEventFromGRPC(event *daemon.ConnectionEvent) *ConnectionEvent {
|
||||
if event == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -384,13 +384,13 @@ func ConnectionEventFromGRPC(event *daemon.ConnectionEvent) *ConnectionEvent {
|
||||
ClosedAt: event.ClosedAt,
|
||||
}
|
||||
if event.Connection != nil {
|
||||
conn := ConnectionFromGRPC(event.Connection)
|
||||
conn := connectionFromGRPC(event.Connection)
|
||||
libboxEvent.Connection = &conn
|
||||
}
|
||||
return libboxEvent
|
||||
}
|
||||
|
||||
func ConnectionEventsFromGRPC(events *daemon.ConnectionEvents) *ConnectionEvents {
|
||||
func connectionEventsFromGRPC(events *daemon.ConnectionEvents) *ConnectionEvents {
|
||||
if events == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -398,14 +398,14 @@ func ConnectionEventsFromGRPC(events *daemon.ConnectionEvents) *ConnectionEvents
|
||||
Reset: events.Reset_,
|
||||
}
|
||||
for _, event := range events.Events {
|
||||
if libboxEvent := ConnectionEventFromGRPC(event); libboxEvent != nil {
|
||||
if libboxEvent := connectionEventFromGRPC(event); libboxEvent != nil {
|
||||
libboxEvents.events = append(libboxEvents.events, libboxEvent)
|
||||
}
|
||||
}
|
||||
return libboxEvents
|
||||
}
|
||||
|
||||
func SystemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxyStatus {
|
||||
func systemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxyStatus {
|
||||
if status == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -415,7 +415,7 @@ func SystemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxySta
|
||||
}
|
||||
}
|
||||
|
||||
func SystemProxyStatusToGRPC(status *SystemProxyStatus) *daemon.SystemProxyStatus {
|
||||
func systemProxyStatusToGRPC(status *SystemProxyStatus) *daemon.SystemProxyStatus {
|
||||
if status == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
)
|
||||
|
||||
func BaseContext(platformInterface PlatformInterface) context.Context {
|
||||
func baseContext(platformInterface PlatformInterface) context.Context {
|
||||
dnsRegistry := include.DNSTransportRegistry()
|
||||
if platformInterface != nil {
|
||||
if localTransport := platformInterface.LocalDNSTransport(); localTransport != nil {
|
||||
@@ -45,7 +45,7 @@ func parseConfig(ctx context.Context, configContent string) (option.Options, err
|
||||
}
|
||||
|
||||
func CheckConfig(configContent string) error {
|
||||
ctx := BaseContext(nil)
|
||||
ctx := baseContext(nil)
|
||||
options, err := parseConfig(ctx, configContent)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -189,7 +189,7 @@ func (s *interfaceMonitorStub) MyInterface() string {
|
||||
}
|
||||
|
||||
func FormatConfig(configContent string) (*StringBox, error) {
|
||||
options, err := parseConfig(BaseContext(nil), configContent)
|
||||
options, err := parseConfig(baseContext(nil), configContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
{
|
||||
"version": 1,
|
||||
"packages": [
|
||||
{
|
||||
"id": "libbox",
|
||||
"path": ".",
|
||||
"java_package": "io.nekohasekai.libbox",
|
||||
"apple_prefix": "Libbox"
|
||||
}
|
||||
],
|
||||
"builds": [
|
||||
{
|
||||
"id": "android-main",
|
||||
"packages": ["libbox"],
|
||||
"default": {
|
||||
"tags": [
|
||||
"with_gvisor",
|
||||
"with_quic",
|
||||
"with_wireguard",
|
||||
"with_utls",
|
||||
"with_naive_outbound",
|
||||
"with_clash_api",
|
||||
"with_conntrack",
|
||||
"badlinkname",
|
||||
"tfogo_checklinkname0",
|
||||
"with_tailscale",
|
||||
"ts_omit_logtail",
|
||||
"ts_omit_ssh",
|
||||
"ts_omit_drive",
|
||||
"ts_omit_taildrop",
|
||||
"ts_omit_webclient",
|
||||
"ts_omit_doctor",
|
||||
"ts_omit_capture",
|
||||
"ts_omit_kube",
|
||||
"ts_omit_aws",
|
||||
"ts_omit_synology",
|
||||
"ts_omit_bird"
|
||||
],
|
||||
"ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0",
|
||||
"trimpath": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "android-legacy",
|
||||
"packages": ["libbox"],
|
||||
"default": {
|
||||
"tags": [
|
||||
"with_gvisor",
|
||||
"with_quic",
|
||||
"with_wireguard",
|
||||
"with_utls",
|
||||
"with_clash_api",
|
||||
"with_conntrack",
|
||||
"badlinkname",
|
||||
"tfogo_checklinkname0",
|
||||
"with_tailscale",
|
||||
"ts_omit_logtail",
|
||||
"ts_omit_ssh",
|
||||
"ts_omit_drive",
|
||||
"ts_omit_taildrop",
|
||||
"ts_omit_webclient",
|
||||
"ts_omit_doctor",
|
||||
"ts_omit_capture",
|
||||
"ts_omit_kube",
|
||||
"ts_omit_aws",
|
||||
"ts_omit_synology",
|
||||
"ts_omit_bird"
|
||||
],
|
||||
"ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0",
|
||||
"trimpath": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "apple",
|
||||
"packages": ["libbox"],
|
||||
"default": {
|
||||
"tags": [
|
||||
"with_gvisor",
|
||||
"with_quic",
|
||||
"with_wireguard",
|
||||
"with_utls",
|
||||
"with_naive_outbound",
|
||||
"with_clash_api",
|
||||
"with_conntrack",
|
||||
"badlinkname",
|
||||
"tfogo_checklinkname0",
|
||||
"with_dhcp",
|
||||
"grpcnotrace",
|
||||
"with_tailscale",
|
||||
"ts_omit_logtail",
|
||||
"ts_omit_ssh",
|
||||
"ts_omit_drive",
|
||||
"ts_omit_taildrop",
|
||||
"ts_omit_webclient",
|
||||
"ts_omit_doctor",
|
||||
"ts_omit_capture",
|
||||
"ts_omit_kube",
|
||||
"ts_omit_aws",
|
||||
"ts_omit_synology",
|
||||
"ts_omit_bird"
|
||||
],
|
||||
"ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0",
|
||||
"trimpath": true
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"match": { "os": "ios" },
|
||||
"tags_append": ["with_low_memory"]
|
||||
},
|
||||
{
|
||||
"match": { "os": "tvos" },
|
||||
"tags_append": ["with_low_memory"]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
{
|
||||
"type": "android",
|
||||
"build": "android-main",
|
||||
"min_sdk": 23,
|
||||
"lib_name": "box",
|
||||
"languages": [{ "type": "java" }],
|
||||
"artifacts": [
|
||||
{
|
||||
"type": "aar",
|
||||
"output_path": "libbox.aar"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"build": "android-legacy",
|
||||
"min_sdk": 21,
|
||||
"lib_name": "box",
|
||||
"languages": [{ "type": "java" }],
|
||||
"artifacts": [
|
||||
{
|
||||
"type": "aar",
|
||||
"output_path": "libbox-legacy.aar"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "apple",
|
||||
"build": "apple",
|
||||
"targets": [
|
||||
"ios/arm64",
|
||||
"ios/simulator/arm64",
|
||||
"ios/simulator/amd64",
|
||||
"tvos/arm64",
|
||||
"tvos/simulator/arm64",
|
||||
"tvos/simulator/amd64",
|
||||
"macos/arm64",
|
||||
"macos/amd64"
|
||||
],
|
||||
"languages": [{ "type": "objc" }],
|
||||
"artifacts": [
|
||||
{
|
||||
"type": "xcframework",
|
||||
"module_name": "Libbox",
|
||||
"output_path": "Libbox.xcframework"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user