mirror of
https://github.com/2dust/v2rayN.git
synced 2026-07-27 18:32:05 +03:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a236fab27c | ||
|
|
f8e1186095 | ||
|
|
ca186e2636 | ||
|
|
1e8ba9ad9b | ||
|
|
31e32f174e | ||
|
|
bf376a8fac | ||
|
|
5a9a9b7ab1 | ||
|
|
e7a9aa66dc | ||
|
|
709b9a0958 | ||
|
|
191fe48257 | ||
|
|
48d58d482a | ||
|
|
7100b3f0a5 | ||
|
|
8d94997ba0 | ||
|
|
c97dc1d894 | ||
|
|
b198fd8be7 | ||
|
|
e0c1ff24be | ||
|
|
3cebf492d1 | ||
|
|
ef46f4e7e6 | ||
|
|
81c118c9b2 | ||
|
|
f1b88ccf2a | ||
|
|
ce380b861c |
9
.github/workflows/build-all.yml
vendored
9
.github/workflows/build-all.yml
vendored
@@ -9,8 +9,17 @@ on:
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
public-key:
|
||||
if: inputs.release_tag != ''
|
||||
uses: ./.github/workflows/pub-key.yml
|
||||
with:
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
116
.github/workflows/build-linux.yml
vendored
116
.github/workflows/build-linux.yml
vendored
@@ -12,6 +12,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -26,6 +27,7 @@ jobs:
|
||||
with:
|
||||
target: linux
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
secrets: inherit
|
||||
|
||||
deb:
|
||||
name: build and release deb x64 & arm64
|
||||
@@ -56,9 +58,9 @@ jobs:
|
||||
find "$GITHUB_WORKSPACE/dist/deb" -name "v2rayn_*_arm64.deb" -exec mv {} "$GITHUB_WORKSPACE/dist/deb/v2rayN-linux-arm64.deb" \; || true
|
||||
echo "==== Dist tree ===="
|
||||
ls -R "$GITHUB_WORKSPACE/dist/deb" || true
|
||||
- name: Upload DEBs to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with: { file: "dist/deb/**/*.deb", tag: "${{ env.RELEASE_TAG }}", file_glob: true, prerelease: true }
|
||||
- name: Upload DEB artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with: { name: release-deb, path: "dist/deb/**/*.deb", if-no-files-found: error }
|
||||
|
||||
rpm:
|
||||
name: build and release rpm x64 & arm64
|
||||
@@ -115,9 +117,9 @@ jobs:
|
||||
find "$GITHUB_WORKSPACE/dist/rpm" -name "v2rayN-*-1*.aarch64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm/v2rayN-linux-rhel-arm64.rpm" \; || true
|
||||
echo "==== Dist tree ===="
|
||||
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
|
||||
- name: Upload RPMs to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with: { file: "dist/rpm/**/*.rpm", tag: "${{ env.RELEASE_TAG }}", file_glob: true, prerelease: true }
|
||||
- name: Upload RPM artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with: { name: release-rpm, path: "dist/rpm/**/*.rpm", if-no-files-found: error }
|
||||
|
||||
rpm-riscv64:
|
||||
name: build and release rpm riscv64
|
||||
@@ -155,25 +157,9 @@ jobs:
|
||||
find "$GITHUB_WORKSPACE/dist/rpm-riscv64" -name "*.riscv64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm-riscv64/v2rayN-linux-rhel-riscv64.rpm" \; || true
|
||||
echo "==== Dist tree ===="
|
||||
ls -R "$GITHUB_WORKSPACE/dist/rpm-riscv64" || true
|
||||
- name: Upload RPMs to release
|
||||
shell: bash
|
||||
env: { GITHUB_TOKEN: "${{ github.token }}" }
|
||||
run: |
|
||||
set -euo pipefail
|
||||
shopt -s globstar nullglob
|
||||
files=(dist/rpm-riscv64/**/*.rpm)
|
||||
(( ${#files[@]} )) || { echo "No RPMs found."; exit 1; }
|
||||
api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${RELEASE_TAG}"
|
||||
upload_url="$(curl -fsSL -H "Authorization: Bearer ${GITHUB_TOKEN}" "$api" | jq -r '.upload_url // empty' | sed 's/{?name,label}//')"
|
||||
[[ "$upload_url" ]] || { echo "Release upload URL not found: ${RELEASE_TAG}"; exit 1; }
|
||||
for f in "${files[@]}"; do
|
||||
echo "Uploading ${f##*/}"
|
||||
curl -fsSL -X POST \
|
||||
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
-H "Content-Type: application/x-rpm" \
|
||||
--data-binary @"$f" \
|
||||
"${upload_url}?name=${f##*/}"
|
||||
done
|
||||
- name: Upload RPM artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with: { name: release-rpm-riscv64, path: "dist/rpm-riscv64/**/*.rpm", if-no-files-found: error }
|
||||
|
||||
deb-riscv64:
|
||||
name: build and release deb riscv64
|
||||
@@ -212,25 +198,9 @@ jobs:
|
||||
find "$GITHUB_WORKSPACE/dist/deb-riscv64" -name "*.deb" -exec mv {} "$GITHUB_WORKSPACE/dist/deb-riscv64/v2rayN-linux-riscv64.deb" \; || true
|
||||
echo "==== Dist tree ===="
|
||||
ls -R "$GITHUB_WORKSPACE/dist/deb-riscv64" || true
|
||||
- name: Upload DEBs to release
|
||||
shell: bash
|
||||
env: { GITHUB_TOKEN: "${{ github.token }}" }
|
||||
run: |
|
||||
set -euo pipefail
|
||||
shopt -s globstar nullglob
|
||||
files=(dist/deb-riscv64/**/*.deb)
|
||||
(( ${#files[@]} )) || { echo "No DEBs found."; exit 1; }
|
||||
api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${RELEASE_TAG}"
|
||||
upload_url="$(curl -fsSL -H "Authorization: Bearer ${GITHUB_TOKEN}" "$api" | jq -r '.upload_url // empty' | sed 's/{?name,label}//')"
|
||||
[[ "$upload_url" ]] || { echo "Release upload URL not found: ${RELEASE_TAG}"; exit 1; }
|
||||
for f in "${files[@]}"; do
|
||||
echo "Uploading ${f##*/}"
|
||||
curl -fsSL -X POST \
|
||||
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
-H "Content-Type: application/vnd.debian.binary-package" \
|
||||
--data-binary @"$f" \
|
||||
"${upload_url}?name=${f##*/}"
|
||||
done
|
||||
- name: Upload DEB artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with: { name: release-deb-riscv64, path: "dist/deb-riscv64/**/*.deb", if-no-files-found: error }
|
||||
|
||||
deb-loong64:
|
||||
name: build and release deb loong64
|
||||
@@ -244,7 +214,7 @@ jobs:
|
||||
QCOW2_IMAGE: debian13-loong64.qcow2
|
||||
EFI_CODE: edk2-loongarch64-code.fd
|
||||
EFI_VARS: edk2-loongarch64-vars.fd
|
||||
QEMU_VERSION: 10.2.3
|
||||
QEMU_VERSION: 10.2.4
|
||||
steps:
|
||||
- name: Prepare host tools
|
||||
shell: bash
|
||||
@@ -378,9 +348,9 @@ jobs:
|
||||
find "$GITHUB_WORKSPACE/dist/deb-loong64" -name "*.deb" -exec mv {} "$GITHUB_WORKSPACE/dist/deb-loong64/v2rayN-linux-loong64.deb" \; || true
|
||||
echo "==== Dist tree ===="
|
||||
ls -R "$GITHUB_WORKSPACE/dist/deb-loong64"
|
||||
- name: Upload DEBs to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with: { file: "dist/deb-loong64/**/*.deb", tag: "${{ env.RELEASE_TAG }}", file_glob: true, prerelease: true }
|
||||
- name: Upload DEB artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with: { name: release-deb-loong64, path: "dist/deb-loong64/**/*.deb", if-no-files-found: error }
|
||||
|
||||
rpm-loong64:
|
||||
name: build and release rpm loong64
|
||||
@@ -394,7 +364,7 @@ jobs:
|
||||
QCOW2_IMAGE: fedora43-loong64.qcow2
|
||||
EFI_CODE: edk2-loongarch64-code.fd
|
||||
EFI_VARS: edk2-loongarch64-vars.fd
|
||||
QEMU_VERSION: 10.2.3
|
||||
QEMU_VERSION: 10.2.4
|
||||
steps:
|
||||
- name: Prepare host tools
|
||||
shell: bash
|
||||
@@ -528,6 +498,48 @@ jobs:
|
||||
find "$GITHUB_WORKSPACE/dist/rpm-loong64" -name "*.loongarch64.rpm" -exec mv {} "$GITHUB_WORKSPACE/dist/rpm-loong64/v2rayN-linux-rhel-loong64.rpm" \; || true
|
||||
echo "==== Dist tree ===="
|
||||
ls -R "$GITHUB_WORKSPACE/dist/rpm-loong64"
|
||||
- name: Upload RPMs to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with: { file: "dist/rpm-loong64/**/*.rpm", tag: "${{ env.RELEASE_TAG }}", file_glob: true, prerelease: true }
|
||||
- name: Upload RPM artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with: { name: release-rpm-loong64, path: "dist/rpm-loong64/**/*.rpm", if-no-files-found: error }
|
||||
|
||||
release-deb:
|
||||
name: sign and upload deb x64 arm64
|
||||
needs: deb
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-deb, file_pattern: "*.deb" }
|
||||
secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" }
|
||||
|
||||
release-rpm:
|
||||
name: sign and upload rpm x64 arm64
|
||||
needs: rpm
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-rpm, file_pattern: "*.rpm" }
|
||||
secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" }
|
||||
|
||||
release-rpm-riscv64:
|
||||
name: sign and upload rpm riscv64
|
||||
needs: rpm-riscv64
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-rpm-riscv64, file_pattern: "*.rpm" }
|
||||
secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" }
|
||||
|
||||
release-deb-riscv64:
|
||||
name: sign and upload deb riscv64
|
||||
needs: deb-riscv64
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-deb-riscv64, file_pattern: "*.deb" }
|
||||
secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" }
|
||||
|
||||
release-deb-loong64:
|
||||
name: sign and upload deb loong64
|
||||
needs: deb-loong64
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-deb-loong64, file_pattern: "*.deb" }
|
||||
secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" }
|
||||
|
||||
release-rpm-loong64:
|
||||
name: sign and upload rpm loong64
|
||||
needs: rpm-loong64
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with: { release_tag: "${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}", artifact_name: release-rpm-loong64, file_pattern: "*.rpm" }
|
||||
secrets: { GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}" }
|
||||
|
||||
31
.github/workflows/build-osx.yml
vendored
31
.github/workflows/build-osx.yml
vendored
@@ -12,6 +12,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -26,6 +27,7 @@ jobs:
|
||||
with:
|
||||
target: macos
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
secrets: inherit
|
||||
|
||||
dmg:
|
||||
name: package and release macOS dmg
|
||||
@@ -62,13 +64,24 @@ jobs:
|
||||
- name: Package dmg
|
||||
run: ./package-osx.sh macos-$Arch v2rayN-macos-$Arch ${{ inputs.release_tag }}
|
||||
|
||||
- name: Sleep for race condition between matrix jobs
|
||||
run: sleep "$(od -An -N2 -tu2 /dev/urandom | awk 'NR==1{printf "%.2f", $1/5461}')"
|
||||
|
||||
- name: Upload dmg to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
- name: Upload dmg artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
file: ${{ github.workspace }}/v2rayN*.dmg
|
||||
tag: ${{ inputs.release_tag }}
|
||||
file_glob: true
|
||||
prerelease: true
|
||||
name: release-dmg-${{ matrix.arch }}
|
||||
path: v2rayN*.dmg
|
||||
if-no-files-found: error
|
||||
|
||||
release-dmg:
|
||||
name: sign and upload macOS dmg
|
||||
if: inputs.release_tag != ''
|
||||
needs: dmg
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [ x64, arm64 ]
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with:
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
artifact_name: release-dmg-${{ matrix.arch }}
|
||||
file_pattern: '*.dmg'
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
|
||||
2
.github/workflows/build-windows-desktop.yml
vendored
2
.github/workflows/build-windows-desktop.yml
vendored
@@ -12,6 +12,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -26,3 +27,4 @@ jobs:
|
||||
with:
|
||||
target: windows-desktop
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
secrets: inherit
|
||||
|
||||
30
.github/workflows/build-windows-x86.yml
vendored
30
.github/workflows/build-windows-x86.yml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
package-x86:
|
||||
@@ -51,7 +52,7 @@ jobs:
|
||||
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
||||
|
||||
- name: Setup .NET 10.0.1xx
|
||||
uses: actions/setup-dotnet@v5.3.0
|
||||
uses: actions/setup-dotnet@v5.4.0
|
||||
with:
|
||||
dotnet-version: 10.0.1xx
|
||||
|
||||
@@ -222,10 +223,25 @@ jobs:
|
||||
-DestinationPath "${{ matrix.zip_name }}" `
|
||||
-Force
|
||||
|
||||
- name: Upload zip archive to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
- name: Upload zip artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
file: ${{ matrix.zip_name }}
|
||||
tag: ${{ inputs.release_tag }}
|
||||
prerelease: true
|
||||
overwrite: true
|
||||
name: release-windows-x86-${{ matrix.flavor }}
|
||||
path: ${{ matrix.zip_name }}
|
||||
if-no-files-found: error
|
||||
|
||||
release-x86:
|
||||
name: sign and upload windows x86
|
||||
if: inputs.release_tag != ''
|
||||
needs: package-x86
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavor: [ wpf, desktop ]
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with:
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
artifact_name: release-windows-x86-${{ matrix.flavor }}
|
||||
file_pattern: '*.zip'
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
|
||||
2
.github/workflows/build-windows.yml
vendored
2
.github/workflows/build-windows.yml
vendored
@@ -12,6 +12,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -27,3 +28,4 @@ jobs:
|
||||
with:
|
||||
target: windows
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
secrets: inherit
|
||||
|
||||
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@@ -22,6 +22,7 @@ jobs:
|
||||
case(
|
||||
inputs.target == 'macos', 'macos-latest',
|
||||
inputs.target == 'linux', 'ubuntu-24.04',
|
||||
inputs.target == 'windows', 'windows-latest',
|
||||
'ubuntu-latest'
|
||||
)
|
||||
}}
|
||||
@@ -68,15 +69,17 @@ jobs:
|
||||
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5.3.0
|
||||
uses: actions/setup-dotnet@v5.4.0
|
||||
with:
|
||||
dotnet-version: '10.0.1xx'
|
||||
|
||||
- name: Build v2rayN
|
||||
shell: bash
|
||||
working-directory: ./v2rayN
|
||||
run: dotnet publish $Project -c Release -r $RID -p:SelfContained=true $ExtOpt -o $Output
|
||||
|
||||
- name: Build AmazTool
|
||||
shell: bash
|
||||
working-directory: ./v2rayN
|
||||
run: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r $RID -p:SelfContained=true -p:PublishTrimmed=true $ExtOpt -o $Output
|
||||
|
||||
|
||||
32
.github/workflows/package-zip.yml
vendored
32
.github/workflows/package-zip.yml
vendored
@@ -9,9 +9,13 @@ on:
|
||||
target: # windows linux macos windows-desktop
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY:
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
package:
|
||||
@@ -55,13 +59,23 @@ jobs:
|
||||
if: inputs.target == 'windows-desktop'
|
||||
run: mv "v2rayN-$Target-$Arch.zip" "v2rayN-$Target-$Arch-desktop.zip"
|
||||
|
||||
- name: Sleep for race condition between matrix jobs
|
||||
run: sleep "$(od -An -N2 -tu2 /dev/urandom | awk 'NR==1{printf "%.2f", $1/5461}')"
|
||||
|
||||
- name: Upload zip archive to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
- name: Upload zip artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
file: ${{ github.workspace }}/v2rayN*.zip
|
||||
tag: ${{ inputs.release_tag }}
|
||||
file_glob: true
|
||||
prerelease: true
|
||||
name: release-${{ inputs.target }}-${{ matrix.arch }}
|
||||
path: v2rayN*.zip
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
name: sign and upload zip x64 arm64
|
||||
needs: package
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [ x64, arm64 ]
|
||||
uses: ./.github/workflows/upload-sign.yml
|
||||
with:
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
artifact_name: release-${{ inputs.target }}-${{ matrix.arch }}
|
||||
file_pattern: '*.zip'
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
|
||||
48
.github/workflows/pub-key.yml
vendored
Normal file
48
.github/workflows/pub-key.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: publish GPG public key
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY:
|
||||
required: true
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Import GPG private key
|
||||
shell: bash
|
||||
env:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
install -m 700 -d ~/.gnupg
|
||||
printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import
|
||||
|
||||
- name: Export GPG public key
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
fingerprint="$(gpg --batch --list-secret-keys --with-colons | awk -F: '$1 == "fpr" { print $10; exit }')"
|
||||
[[ -n "$fingerprint" ]] || { echo "No GPG secret key found."; exit 1; }
|
||||
gpg --batch --armor --export "$fingerprint" > v2rayN-public-key.asc
|
||||
|
||||
- name: Upload GPG public key to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
file: v2rayN-public-key.asc
|
||||
tag: ${{ inputs.release_tag }}
|
||||
prerelease: true
|
||||
overwrite: true
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5.3.0
|
||||
uses: actions/setup-dotnet@v5.4.0
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
|
||||
76
.github/workflows/upload-sign.yml
vendored
Normal file
76
.github/workflows/upload-sign.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
name: sign and upload release files
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
required: true
|
||||
type: string
|
||||
artifact_name:
|
||||
required: true
|
||||
type: string
|
||||
file_pattern:
|
||||
required: false
|
||||
type: string
|
||||
default: '**/*'
|
||||
content_type:
|
||||
required: false
|
||||
type: string
|
||||
default: application/octet-stream
|
||||
secrets:
|
||||
GPG_PRIVATE_KEY:
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Download release artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}
|
||||
path: dist
|
||||
|
||||
- name: Import GPG private key
|
||||
shell: bash
|
||||
env:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
install -m 700 -d ~/.gnupg
|
||||
printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import
|
||||
|
||||
- name: Sign release files
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd dist
|
||||
shopt -s globstar nullglob
|
||||
files=( ${{ inputs.file_pattern }} )
|
||||
(( ${#files[@]} )) || { echo "No files matched: ${{ inputs.file_pattern }}"; exit 1; }
|
||||
for f in "${files[@]}"; do
|
||||
[[ -f "$f" ]] || continue
|
||||
gpg --batch --yes --armor --detach-sign --output "$f.sig" "$f"
|
||||
done
|
||||
|
||||
- name: Upload release files
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
file: dist/${{ inputs.file_pattern }}
|
||||
tag: ${{ inputs.release_tag }}
|
||||
file_glob: true
|
||||
prerelease: true
|
||||
overwrite: true
|
||||
|
||||
- name: Upload release signatures
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
file: dist/${{ inputs.file_pattern }}.sig
|
||||
tag: ${{ inputs.release_tag }}
|
||||
file_glob: true
|
||||
prerelease: true
|
||||
overwrite: true
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -84,6 +84,9 @@ StyleCopReport.xml
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
bin/
|
||||
obj/
|
||||
build/
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "v2rayN/GlobalHotKeys"]
|
||||
path = v2rayN/GlobalHotKeys
|
||||
url = https://github.com/2dust/GlobalHotKeys
|
||||
[submodule "v2rayN/NetBridge"]
|
||||
path = v2rayN/NetBridge
|
||||
url = https://github.com/2dust/NetBridge
|
||||
|
||||
80
README.md
80
README.md
@@ -1,18 +1,78 @@
|
||||
# v2rayN
|
||||
|
||||
A GUI client for Windows, Linux and macOS, support [Xray](https://github.com/XTLS/Xray-core)
|
||||
and [sing-box](https://github.com/SagerNet/sing-box)
|
||||
and [others](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores)
|
||||
### A GUI client for Windows, Linux and macOS. Support [Xray](https://github.com/XTLS/Xray-core) and [sing-box](https://github.com/SagerNet/sing-box) and [others](https://github.com/2dust/v2rayN/wiki/List-of-supported-cores)
|
||||
|
||||
[](https://github.com/2dust/v2rayN/commits/master)
|
||||
[](https://www.codefactor.io/repository/github/2dust/v2rayn)
|
||||
[](https://github.com/2dust/v2rayN/releases)
|
||||
[](https://t.me/v2rayn)
|
||||
[](https://github.com/2dust/v2rayN/releases)
|
||||
[](https://github.com/2dust/v2rayN/releases)
|
||||
[](https://t.me/v2rayn)
|
||||
|
||||
[](https://github.com/2dust/v2rayN)
|
||||
[](https://github.com/2dust/v2rayN)
|
||||
[](https://github.com/2dust/v2rayN)
|
||||
[](https://github.com/2dust/v2rayN)
|
||||
|
||||
## How to use
|
||||
|
||||
Read the [Wiki](https://github.com/2dust/v2rayN/wiki) for details.
|
||||
---
|
||||
|
||||
## Telegram Channel
|
||||
## Download / 下载
|
||||
|
||||
[github_2dust](https://t.me/github_2dust)
|
||||
Download the latest release here:
|
||||
|
||||
在这里下载最新版本:
|
||||
|
||||
[https://github.com/2dust/v2rayN/releases](https://github.com/2dust/v2rayN/releases)
|
||||
|
||||
|
||||
> [!TIP]
|
||||
> v2rayN is the desktop version. For the mobile version, please visit the v2rayNG \
|
||||
> v2rayN 是电脑版,手机版请访问 v2rayNG
|
||||
>
|
||||
> https://github.com/2dust/v2rayNG
|
||||
|
||||
---
|
||||
|
||||
## Documentation / 使用文档
|
||||
|
||||
Read the Wiki for usage guides and configuration details.
|
||||
|
||||
请阅读 Wiki 获取使用说明和配置教程。
|
||||
|
||||
[https://github.com/2dust/v2rayN/wiki](https://github.com/2dust/v2rayN/wiki)
|
||||
|
||||
---
|
||||
|
||||
## Supported Platforms / 支持平台
|
||||
|
||||
| Platform / 平台 | x64 | x86 | arm64 | riscv64 | loong64 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Windows | ✅ | ✅ | ✅ | - | - |
|
||||
| Linux | ✅ | - | ✅ | ✅ | ✅ |
|
||||
| macOS | ✅ | - | ✅ | - | - |
|
||||
|
||||
---
|
||||
|
||||
## GPG Verification / GPG 签名校验
|
||||
|
||||
Release files are signed with GPG to verify authenticity and integrity, helping prevent mirror, ISP, or CDN hijacking.
|
||||
|
||||
发布文件已使用 GPG 签名,可用于校验文件真实性与完整性,预防镜像站、运营商或 CDN 劫持。
|
||||
|
||||
### Fingerprint / 公钥指纹
|
||||
|
||||
```text
|
||||
7694 5E9F 3E9A 168F 8070 F195 805D 661C
|
||||
134D FAF6 8903 C199 463C 31E5 AE90 3AE0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Community / 社区
|
||||
|
||||
Telegram Group / Telegram 群组:
|
||||
|
||||
[https://t.me/v2rayN](https://t.me/v2rayN)
|
||||
|
||||
Telegram Channel / Telegram 频道:
|
||||
|
||||
[https://t.me/github_2dust](https://t.me/github_2dust)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.23.0</Version>
|
||||
<Version>7.23.2</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.17" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.17" />
|
||||
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
|
||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" />
|
||||
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.13" />
|
||||
<PackageVersion Include="CliWrap" Version="3.10.1" />
|
||||
<PackageVersion Include="Downloader" Version="5.7.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
|
||||
<PackageVersion Include="QRCoder" Version="1.8.0" />
|
||||
<PackageVersion Include="ReactiveUI" Version="23.2.28" />
|
||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="23.2.28" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="11.3.14" />
|
||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.2" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
|
||||
<PackageVersion Include="NLog" Version="6.1.3" />
|
||||
<PackageVersion Include="sqlite-net-e" Version="1.11.0" />
|
||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.2" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
||||
<PackageVersion Include="YamlDotNet" Version="18.0.0" />
|
||||
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.18" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.18" />
|
||||
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
|
||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" />
|
||||
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.13" />
|
||||
<PackageVersion Include="CliWrap" Version="3.10.2" />
|
||||
<PackageVersion Include="Downloader" Version="5.9.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
|
||||
<PackageVersion Include="QRCoder" Version="1.8.0" />
|
||||
<PackageVersion Include="ReactiveUI" Version="23.2.28" />
|
||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="23.2.28" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="11.3.14" />
|
||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.2" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
|
||||
<PackageVersion Include="NLog" Version="6.1.3" />
|
||||
<PackageVersion Include="sqlite-net-e" Version="1.11.0" />
|
||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.3" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
||||
<PackageVersion Include="YamlDotNet" Version="18.0.0" />
|
||||
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Submodule v2rayN/NetBridge deleted from e9b16cd187
@@ -1,8 +1,10 @@
|
||||
using AwesomeAssertions;
|
||||
using ServiceLib.Common;
|
||||
using ServiceLib.Enums;
|
||||
using ServiceLib.Handler.Fmt;
|
||||
using ServiceLib.Manager;
|
||||
using ServiceLib.Models;
|
||||
using ServiceLib.Models.Dto;
|
||||
using ServiceLib.Services.CoreConfig;
|
||||
using Xunit;
|
||||
|
||||
@@ -557,4 +559,37 @@ public class CoreConfigSingboxServiceTests
|
||||
cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Global));
|
||||
cfg.dns.rules.Should().Contain(r => r.clash_mode == nameof(ERuleMode.Direct));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateClientConfigContent_Hysteria2Realm_ShouldEmitHttpsServerUrl()
|
||||
{
|
||||
var shareLink =
|
||||
"hysteria2+realm://public@realm.hy2.io/my-realm-id?auth=uuid&stun=turn.cloudflare.com%3A3478&sni=cloudflare.com&pinSHA256=xxx#Realm-Test";
|
||||
var node = Hysteria2Fmt.ResolveRealm(shareLink, out _);
|
||||
node.Should().NotBeNull();
|
||||
node!.CoreType = ECoreType.sing_box;
|
||||
|
||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||
config.CoreTypeItem =
|
||||
[
|
||||
new CoreTypeItem { ConfigType = EConfigType.Hysteria2, CoreType = ECoreType.sing_box }
|
||||
];
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box);
|
||||
|
||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||
|
||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString())!;
|
||||
var proxy = cfg.outbounds.First(o => o.tag == Global.ProxyTag);
|
||||
|
||||
proxy.type.Should().Be("hysteria2");
|
||||
proxy.realm.Should().NotBeNull();
|
||||
proxy.realm!.server_url.Should().StartWith("https://");
|
||||
proxy.realm.server_url.Should().Contain("realm.hy2.io");
|
||||
proxy.realm.token.Should().Be("public");
|
||||
proxy.realm.realm_id.Should().Be("my-realm-id");
|
||||
proxy.realm.stun_servers.Should().Contain("turn.cloudflare.com:3478");
|
||||
proxy.server.Should().BeNull();
|
||||
}
|
||||
}
|
||||
|
||||
91
v2rayN/ServiceLib.Tests/Fmt/HyRealmTests.cs
Normal file
91
v2rayN/ServiceLib.Tests/Fmt/HyRealmTests.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using AwesomeAssertions;
|
||||
using ServiceLib.Handler.Fmt;
|
||||
using ServiceLib.Models.Dto;
|
||||
using Xunit;
|
||||
|
||||
namespace ServiceLib.Tests.Fmt;
|
||||
|
||||
public class HyRealmTests
|
||||
{
|
||||
[Fact]
|
||||
public void TryParse_ShouldParseValidRealm()
|
||||
{
|
||||
var str = "realm://public@realm.hy2.io/57f9be7c-2810-4f5b-8cb9-260bc84d6c90?stun=example.stun:3478&stun=example2.stun:3478";
|
||||
var result = HyRealm.TryParse(str, out var realm);
|
||||
result.Should().BeTrue();
|
||||
realm.Should().NotBeNull();
|
||||
|
||||
realm.IsHttp.Should().BeFalse();
|
||||
realm.Token.Should().Be("public");
|
||||
realm.RendezvousHost.Should().Be("realm.hy2.io");
|
||||
realm.RendezvousPort.Should().Be(443);
|
||||
realm.RealmName.Should().Be("57f9be7c-2810-4f5b-8cb9-260bc84d6c90");
|
||||
realm.StunList.Should().HaveCount(2);
|
||||
realm.StunList.Should().Contain("example.stun:3478");
|
||||
realm.StunList.Should().Contain("example2.stun:3478");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToUri_ShouldGenerateValidUri()
|
||||
{
|
||||
var realm = new HyRealm(
|
||||
IsHttp: false,
|
||||
Token: "public",
|
||||
RendezvousHost: "realm.hy2.io",
|
||||
RendezvousPort: 443,
|
||||
RealmName: "57f9be7c-2810-4f5b-8cb9-260bc84d6c90",
|
||||
StunList: ["example.stun:3478", "example2.stun:3478"]
|
||||
);
|
||||
var uri = realm.ToUri();
|
||||
uri.Should().Contain("realm://public@realm.hy2.io");
|
||||
uri.Should().Contain("/57f9be7c-2810-4f5b-8cb9-260bc84d6c90");
|
||||
uri.Should().Contain("stun=example.stun:3478");
|
||||
uri.Should().Contain("stun=example2.stun:3478");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetShareUriAndResolveConfig_Hy2Realm_ShouldRoundTripBasicFields()
|
||||
{
|
||||
var str = "hysteria2+realm://mytoken@rendezvous.example.com/my-cabin-1f3a8c2e9b?auth=your_password&insecure=1&pinSHA256=deadbeef#remark";
|
||||
var resolved = Hysteria2Fmt.ResolveRealm(str, out var msg);
|
||||
resolved.Should().NotBeNull();
|
||||
resolved.Password.Should().Be("your_password");
|
||||
var result = HyRealm.TryParse(resolved.GetProtocolExtra().Hy2RealmUrl, out var realm);
|
||||
result.Should().BeTrue();
|
||||
realm.Should().NotBeNull();
|
||||
realm.Token.Should().Be("mytoken");
|
||||
|
||||
// To uri
|
||||
var uri = Hysteria2Fmt.ToUri(resolved);
|
||||
uri.Should().Contain("hysteria2+realm://mytoken@rendezvous.example.com");
|
||||
uri.Should().EndWith("#remark");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToServerUrl_ShouldIncludeSchemeForSingbox()
|
||||
{
|
||||
var realm = new HyRealm(
|
||||
IsHttp: false,
|
||||
Token: "public",
|
||||
RendezvousHost: "realm.hy2.io",
|
||||
RendezvousPort: 443,
|
||||
RealmName: "my-realm-id",
|
||||
StunList: ["turn.cloudflare.com:3478"]
|
||||
);
|
||||
|
||||
realm.ToServerUrl().Should().Be("https://realm.hy2.io:443");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveRealm_Issue9635_ShouldProduceHttpsServerUrl()
|
||||
{
|
||||
var str = "hysteria2+realm://public@realm.hy2.io/my-realm-id?auth=uuid&stun=turn.cloudflare.com%3A3478&sni=cloudflare.com&pinSHA256=xxx#Realm-Test";
|
||||
var resolved = Hysteria2Fmt.ResolveRealm(str, out _);
|
||||
resolved.Should().NotBeNull();
|
||||
|
||||
HyRealm.TryParse(resolved!.GetProtocolExtra().Hy2RealmUrl, out var realm).Should().BeTrue();
|
||||
realm!.ToServerUrl().Should().StartWith("https://");
|
||||
realm.ToServerUrl().Should().Contain("realm.hy2.io");
|
||||
realm.StunList.Should().Contain("turn.cloudflare.com:3478");
|
||||
}
|
||||
}
|
||||
@@ -324,6 +324,29 @@ public class Utils
|
||||
.ReplaceLineBreaks(",");
|
||||
}
|
||||
|
||||
public static string ParseProcess(string text)
|
||||
{
|
||||
if (text.IsNullOrEmpty())
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
if (text.StartsWith('"'))
|
||||
{
|
||||
text = text[1..];
|
||||
}
|
||||
if (text.EndsWith('"'))
|
||||
{
|
||||
text = text[..^1];
|
||||
}
|
||||
return List2String(text.Replace(",", ",")
|
||||
.Replace("\\", "/")
|
||||
.ReplaceLineBreaks(",")
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.TrimEx())
|
||||
.Where(x => x.IsNotEmpty())
|
||||
.ToList());
|
||||
}
|
||||
|
||||
public static List<string> GetEnumNames<TEnum>() where TEnum : Enum
|
||||
{
|
||||
return Enum.GetValues(typeof(TEnum))
|
||||
@@ -576,6 +599,58 @@ public class Utils
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryParseRange(string? input, int min, int max, out int from, out int to)
|
||||
{
|
||||
from = to = 0;
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var parts = input.Split('-');
|
||||
if (parts.Length == 1)
|
||||
{
|
||||
if (!int.TryParse(parts[0], out from))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
to = from;
|
||||
return from >= min && to <= max;
|
||||
}
|
||||
if (parts.Length != 2
|
||||
|| !int.TryParse(parts[0], out from)
|
||||
|| !int.TryParse(parts[1], out to))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return from >= min && to <= max && from <= to;
|
||||
}
|
||||
|
||||
public static bool TryParseMaxSplit(string? input, int min, int max, out int from, out int to)
|
||||
{
|
||||
from = to = 0;
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var parts = input.Split('-');
|
||||
if (parts.Length == 1)
|
||||
{
|
||||
if (!int.TryParse(parts[0], out from))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
to = from;
|
||||
return from >= min && to <= max;
|
||||
}
|
||||
if (parts.Length != 2
|
||||
|| !int.TryParse(parts[0], out from)
|
||||
|| !int.TryParse(parts[1], out to))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return from >= min && to <= max && from <= to;
|
||||
}
|
||||
|
||||
public static bool IsPrivateNetwork(string ip)
|
||||
{
|
||||
if (IPAddress.TryParse(ip, out var address))
|
||||
@@ -728,6 +803,12 @@ public class Utils
|
||||
.Any(ua => ua.Address.Equals(targetAddress));
|
||||
}
|
||||
|
||||
public static bool ContainsInterfaceName(string inInterfaceName)
|
||||
{
|
||||
return NetworkInterface.GetAllNetworkInterfaces()
|
||||
.Any(ni => ni.Name.Equals(inInterfaceName, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
#endregion Speed Test
|
||||
|
||||
#region Miscellaneous
|
||||
|
||||
@@ -92,6 +92,7 @@ public class Global
|
||||
public const string LinuxBash = "/bin/bash";
|
||||
public const string StringTrue = "true";
|
||||
public const string StringFalse = "false";
|
||||
public const int SqliteMaxBatchSize = 10000;
|
||||
|
||||
public const string SingboxDirectDNSTag = "direct_dns";
|
||||
public const string SingboxRemoteDNSTag = "remote_dns";
|
||||
@@ -210,6 +211,10 @@ public class Global
|
||||
|
||||
public const string Hysteria2ProtocolShare = "hy2://";
|
||||
|
||||
public const string Hysteria2RealmProtocolShare = "hysteria2+realm://";
|
||||
|
||||
public const string Hysteria2HttpRealmProtocolShare = "hysteria2+realm+http://";
|
||||
|
||||
public const string NaiveHttpsProtocolShare = "naive+https://";
|
||||
|
||||
public const string NaiveQuicProtocolShare = "naive+quic://";
|
||||
@@ -406,6 +411,16 @@ public class Global
|
||||
""
|
||||
];
|
||||
|
||||
public static readonly List<string> FragmentPacketsOptions =
|
||||
[
|
||||
"tlshello",
|
||||
"1-1",
|
||||
"1-2",
|
||||
"1-3",
|
||||
"1-4",
|
||||
"1-5"
|
||||
];
|
||||
|
||||
public static readonly List<string> UserAgent =
|
||||
[
|
||||
"chrome",
|
||||
@@ -667,6 +682,14 @@ public class Global
|
||||
"mcbe:bedrock.talonmc.net",
|
||||
];
|
||||
|
||||
public static readonly List<string> DefaultRealmStunList =
|
||||
[
|
||||
"turn.cloudflare.com:3478",
|
||||
"stun.nextcloud.com:3478",
|
||||
"stun.sip.us:3478",
|
||||
"global.stun.twilio.com:3478",
|
||||
];
|
||||
|
||||
public static readonly List<string> OutboundTags =
|
||||
[
|
||||
ProxyTag,
|
||||
|
||||
@@ -96,17 +96,39 @@ public class CoreConfigContextBuilder
|
||||
}
|
||||
if (context.IsTunEnabled && context.AppConfig.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
||||
{
|
||||
var appConfig = JsonUtils.DeepCopy(config);
|
||||
var routeExcludeAddressList = new List<string>();
|
||||
foreach (var addr in context.AppConfig.TunModeItem.RouteExcludeAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPNetwork2.Parse(addr);
|
||||
routeExcludeAddressList.Add(addr);
|
||||
}
|
||||
catch
|
||||
{
|
||||
validatorResult.Errors.Add(string.Format(ResUI.MsgTunRouteExcludeInvalidAddress, addr));
|
||||
validatorResult.Warnings.Add(string.Format(ResUI.MsgTunRouteExcludeInvalidAddress, addr));
|
||||
}
|
||||
}
|
||||
appConfig.TunModeItem.RouteExcludeAddress = routeExcludeAddressList;
|
||||
context = context with { AppConfig = appConfig };
|
||||
}
|
||||
if (!context.AppConfig.CoreBasicItem.SendThrough.IsNullOrEmpty()
|
||||
&& !Utils.IsLocalIP(context.AppConfig.CoreBasicItem.SendThrough))
|
||||
{
|
||||
validatorResult.Warnings.Add(string.Format(ResUI.MsgInvalidProperty, ResUI.TbSettingsSendThrough));
|
||||
var appConfig = JsonUtils.DeepCopy(config);
|
||||
appConfig.CoreBasicItem.SendThrough = string.Empty;
|
||||
context = context with { AppConfig = appConfig };
|
||||
}
|
||||
if (!context.AppConfig.CoreBasicItem.BindInterface.IsNullOrEmpty()
|
||||
&& (!Utils.ContainsInterfaceName(context.AppConfig.CoreBasicItem.BindInterface)
|
||||
|| !(context.IsTunEnabled || context.IsWindows)))
|
||||
{
|
||||
validatorResult.Warnings.Add(string.Format(ResUI.MsgInvalidProperty, ResUI.TbSettingsBindInterface));
|
||||
var appConfig = JsonUtils.DeepCopy(config);
|
||||
appConfig.CoreBasicItem.BindInterface = string.Empty;
|
||||
context = context with { AppConfig = appConfig };
|
||||
}
|
||||
|
||||
return new CoreConfigContextBuilderResult(context, validatorResult);
|
||||
|
||||
@@ -43,8 +43,8 @@ public class NodeValidator
|
||||
}
|
||||
|
||||
// Basic Property Validation
|
||||
v.Assert(!item.Address.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "Address"));
|
||||
v.Assert(item.Port is > 0 and <= 65535, string.Format(ResUI.MsgInvalidProperty, "Port"));
|
||||
v.Assert(!item.Address.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, ResUI.TbAddress));
|
||||
v.Assert(item.Port is > 0 and <= 65535, string.Format(ResUI.MsgInvalidProperty, ResUI.TbPort));
|
||||
|
||||
// Network & Core Logic
|
||||
var net = item.GetNetwork();
|
||||
@@ -75,25 +75,25 @@ public class NodeValidator
|
||||
{
|
||||
case EConfigType.VMess:
|
||||
v.Assert(!item.Password.IsNullOrEmpty() && Utils.IsGuidByParse(item.Password),
|
||||
string.Format(ResUI.MsgInvalidProperty, "Password"));
|
||||
string.Format(ResUI.MsgInvalidProperty, ResUI.TbId));
|
||||
break;
|
||||
|
||||
case EConfigType.VLESS:
|
||||
v.Assert(
|
||||
!item.Password.IsNullOrEmpty()
|
||||
&& (Utils.IsGuidByParse(item.Password) || item.Password.Length <= 30),
|
||||
string.Format(ResUI.MsgInvalidProperty, "Password")
|
||||
string.Format(ResUI.MsgInvalidProperty, ResUI.TbId5)
|
||||
);
|
||||
v.Assert(Global.Flows.Contains(protocolExtra.Flow ?? string.Empty),
|
||||
string.Format(ResUI.MsgInvalidProperty, "Flow"));
|
||||
string.Format(ResUI.MsgInvalidProperty, ResUI.TbFlow5));
|
||||
break;
|
||||
|
||||
case EConfigType.Shadowsocks:
|
||||
v.Assert(!item.Password.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "Password"));
|
||||
v.Assert(!item.Password.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, ResUI.TbId3));
|
||||
v.Assert(
|
||||
!string.IsNullOrEmpty(protocolExtra.SsMethod) &&
|
||||
Global.SsSecuritiesInSingbox.Contains(protocolExtra.SsMethod),
|
||||
string.Format(ResUI.MsgInvalidProperty, "SsMethod"));
|
||||
string.Format(ResUI.MsgInvalidProperty, ResUI.TbSecurity3));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -115,10 +115,11 @@ public class NodeValidator
|
||||
// TLS & Security
|
||||
if (item.StreamSecurity == Global.StreamSecurity)
|
||||
{
|
||||
if (!item.Cert.IsNullOrEmpty() && CertPemManager.ParsePemChain(item.Cert).Count == 0 &&
|
||||
!item.CertSha.IsNullOrEmpty())
|
||||
var isCertProvided = !item.Cert.IsNullOrEmpty();
|
||||
if (!item.Cert.IsNullOrEmpty() && CertPemManager.ParsePemChain(item.Cert).Count == 0)
|
||||
{
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, "TLS Certificate"));
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, ResUI.TbFullCertTips));
|
||||
isCertProvided = false;
|
||||
}
|
||||
|
||||
// Check for deprecated allowInsecure property when TLS is enabled
|
||||
@@ -131,19 +132,19 @@ public class NodeValidator
|
||||
|
||||
if ((coreType == ECoreType.Xray
|
||||
&& item.GetAllowInsecure()
|
||||
&& item.Cert.IsNullOrEmpty()
|
||||
&& !isCertProvided
|
||||
&& item.CertSha.IsNullOrEmpty())
|
||||
|| (coreType == ECoreType.sing_box
|
||||
&& item.GetAllowInsecure()
|
||||
&& item.Cert.IsNullOrEmpty()))
|
||||
&& !isCertProvided))
|
||||
{
|
||||
v.Warning("Insecure configuration detected: AllowInsecure is enabled but no certificate is provided. This may cause MITM attacks.");
|
||||
v.Warning(ResUI.MsgInsecureConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.StreamSecurity == Global.StreamSecurityReality)
|
||||
{
|
||||
v.Assert(!item.PublicKey.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "PublicKey"));
|
||||
v.Assert(!item.PublicKey.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, ResUI.TbPublicKey));
|
||||
}
|
||||
|
||||
var transport = item.GetTransportExtra();
|
||||
@@ -151,7 +152,7 @@ public class NodeValidator
|
||||
{
|
||||
if (JsonUtils.ParseJson(transport.XhttpExtra) is not JsonObject)
|
||||
{
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, "XHTTP Extra"));
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, ResUI.TransportExtra));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +160,7 @@ public class NodeValidator
|
||||
{
|
||||
if (JsonUtils.ParseJson(item.Finalmask) is not JsonObject)
|
||||
{
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, "Finalmask"));
|
||||
v.Error(string.Format(ResUI.MsgInvalidProperty, ResUI.TbFinalmask));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,16 +160,13 @@ public static class ConfigHandler
|
||||
config.ClashUIItem.ConnectionsColumnItem ??= [];
|
||||
config.SystemProxyItem ??= new();
|
||||
config.WebDavItem ??= new();
|
||||
config.CheckUpdateItem ??= new();
|
||||
config.NetBridgeItem ??= new()
|
||||
{
|
||||
RuleProcess = string.Empty
|
||||
};
|
||||
config.CheckUpdateItem ??= new();
|
||||
config.Fragment4RayItem ??= new()
|
||||
{
|
||||
Packets = "tlshello",
|
||||
Length = "50-100",
|
||||
Interval = "10-20"
|
||||
Interval = "10-20",
|
||||
MaxSplit = "0"
|
||||
};
|
||||
config.GlobalHotkeys ??= [];
|
||||
|
||||
@@ -724,12 +721,51 @@ public static class ConfigHandler
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||
var protocolExtra = profileItem.GetProtocolExtra();
|
||||
profileItem.SetProtocolExtra(protocolExtra with
|
||||
{
|
||||
SalamanderPass = profileItem.GetProtocolExtra().SalamanderPass?.TrimEx(),
|
||||
HopInterval = profileItem.GetProtocolExtra().HopInterval?.TrimEx(),
|
||||
SalamanderPass = protocolExtra.SalamanderPass?.TrimEx(),
|
||||
HopInterval = protocolExtra.HopInterval?.TrimEx(),
|
||||
});
|
||||
|
||||
if (!protocolExtra.Hy2RealmUrl.IsNullOrEmpty())
|
||||
{
|
||||
var realmResult = HyRealm.TryParse(protocolExtra.Hy2RealmUrl, out var realm);
|
||||
if (!realmResult || realm is null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (realm.StunList.Count == 0)
|
||||
{
|
||||
realm = realm with
|
||||
{
|
||||
StunList = Global.DefaultRealmStunList,
|
||||
};
|
||||
}
|
||||
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||
{
|
||||
Hy2RealmUrl = realm.ToUri(),
|
||||
});
|
||||
}
|
||||
var isGecko = !protocolExtra.GeckoMinPacketSize.IsNullOrEmpty() || !protocolExtra.GeckoMaxPacketSize.IsNullOrEmpty();
|
||||
if (isGecko)
|
||||
{
|
||||
var minPacketSize = protocolExtra.GeckoMinPacketSize.ToInt();
|
||||
var maxPacketSize = protocolExtra.GeckoMaxPacketSize.ToInt();
|
||||
if (minPacketSize <= 0
|
||||
|| minPacketSize > maxPacketSize
|
||||
|| maxPacketSize > 2048)
|
||||
{
|
||||
minPacketSize = 512;
|
||||
maxPacketSize = 1200;
|
||||
}
|
||||
profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with
|
||||
{
|
||||
GeckoMinPacketSize = minPacketSize.ToString(),
|
||||
GeckoMaxPacketSize = maxPacketSize.ToString(),
|
||||
});
|
||||
}
|
||||
|
||||
await AddServerCommon(config, profileItem, toFile);
|
||||
|
||||
return 0;
|
||||
@@ -1454,8 +1490,7 @@ public static class ConfigHandler
|
||||
public static ProfileItem? GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType)
|
||||
{
|
||||
ProfileItem? itemSocks = null;
|
||||
var enableLegacyProtect = config.TunModeItem.EnableLegacyProtect
|
||||
|| Utils.IsNonWindows();
|
||||
var enableLegacyProtect = config.TunModeItem.EnableLegacyProtect;
|
||||
if (node.ConfigType != EConfigType.Custom
|
||||
&& coreType != ECoreType.sing_box
|
||||
&& config.TunModeItem.EnableTun
|
||||
|
||||
@@ -72,6 +72,11 @@ public class FmtHandler
|
||||
{
|
||||
return Hysteria2Fmt.Resolve(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.Hysteria2RealmProtocolShare)
|
||||
|| str.StartsWith(Global.Hysteria2HttpRealmProtocolShare))
|
||||
{
|
||||
return Hysteria2Fmt.ResolveRealm(str, out msg);
|
||||
}
|
||||
else if (str.StartsWith(Global.ProtocolShares[EConfigType.TUIC]))
|
||||
{
|
||||
return TuicFmt.Resolve(str, out msg);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class Hysteria2Fmt : BaseFmt
|
||||
@@ -23,15 +25,7 @@ public class Hysteria2Fmt : BaseFmt
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveUriQuery(query, ref item);
|
||||
if (item.CertSha.IsNullOrEmpty())
|
||||
{
|
||||
item.CertSha = GetQueryDecoded(query, "pinSHA256");
|
||||
}
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||
{
|
||||
Ports = GetQueryDecoded(query, "mport"),
|
||||
SalamanderPass = GetQueryDecoded(query, "obfs-password"),
|
||||
});
|
||||
ResolveHy2UriQuery(query, ref item);
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -42,8 +36,11 @@ public class Hysteria2Fmt : BaseFmt
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var url = string.Empty;
|
||||
var protocolExtraItem = item.GetProtocolExtra();
|
||||
if (!protocolExtraItem.Hy2RealmUrl.TrimEx().IsNullOrEmpty())
|
||||
{
|
||||
return ToUriForRealm(item);
|
||||
}
|
||||
|
||||
var remark = string.Empty;
|
||||
if (item.Remarks.IsNotEmpty())
|
||||
@@ -52,27 +49,7 @@ public class Hysteria2Fmt : BaseFmt
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
ToUriQueryLite(item, ref dicQuery);
|
||||
var protocolExtraItem = item.GetProtocolExtra();
|
||||
|
||||
if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty())
|
||||
{
|
||||
dicQuery.Add("obfs", "salamander");
|
||||
dicQuery.Add("obfs-password", Utils.UrlEncode(protocolExtraItem.SalamanderPass));
|
||||
}
|
||||
if (!protocolExtraItem.Ports.IsNullOrEmpty())
|
||||
{
|
||||
dicQuery.Add("mport", Utils.UrlEncode(protocolExtraItem.Ports.Replace(':', '-')));
|
||||
}
|
||||
if (!item.CertSha.IsNullOrEmpty())
|
||||
{
|
||||
var sha = item.CertSha;
|
||||
var idx = sha.IndexOf(',');
|
||||
if (idx > 0)
|
||||
{
|
||||
sha = sha[..idx];
|
||||
}
|
||||
dicQuery.Add("pinSHA256", Utils.UrlEncode(sha));
|
||||
}
|
||||
ToHy2UriQuery(item, ref dicQuery);
|
||||
|
||||
return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Password, dicQuery, remark);
|
||||
}
|
||||
@@ -94,4 +71,155 @@ public class Hysteria2Fmt : BaseFmt
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveRealm(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
ProfileItem item = new()
|
||||
{
|
||||
ConfigType = EConfigType.Hysteria2
|
||||
};
|
||||
var realmStr = str["hysteria2+".Length..];
|
||||
var result = HyRealm.TryParse(realmStr, out var realm);
|
||||
if (!result || realm == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var url = Utils.TryUri(str);
|
||||
if (url == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.Address = realm.RendezvousHost;
|
||||
item.Port = realm.RendezvousPort;
|
||||
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveUriQuery(query, ref item);
|
||||
var authPassword = GetQueryDecoded(query, "auth");
|
||||
if (authPassword.IsNullOrEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
item.Password = authPassword;
|
||||
ResolveHy2UriQuery(query, ref item);
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||
{
|
||||
Hy2RealmUrl = realm.ToUri(),
|
||||
});
|
||||
|
||||
msg = string.Empty;
|
||||
return item;
|
||||
}
|
||||
|
||||
public static string? ToUriForRealm(ProfileItem? item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var protocolExtraItem = item.GetProtocolExtra();
|
||||
var result = HyRealm.TryParse(protocolExtraItem.Hy2RealmUrl, out var realm);
|
||||
if (!result || realm == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var remark = string.Empty;
|
||||
if (item.Remarks.IsNotEmpty())
|
||||
{
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
ToUriQueryLite(item, ref dicQuery);
|
||||
ToHy2UriQuery(item, ref dicQuery);
|
||||
|
||||
dicQuery.Add("auth", Utils.UrlEncode(item.Password));
|
||||
|
||||
var queryBuilder = new StringBuilder();
|
||||
queryBuilder.Append('?');
|
||||
foreach (var kv in dicQuery)
|
||||
{
|
||||
queryBuilder.Append(kv.Key);
|
||||
queryBuilder.Append('=');
|
||||
queryBuilder.Append(kv.Value);
|
||||
queryBuilder.Append('&');
|
||||
}
|
||||
foreach (var stun in realm.StunList)
|
||||
{
|
||||
queryBuilder.Append("stun=");
|
||||
queryBuilder.Append(Utils.UrlEncode(stun));
|
||||
queryBuilder.Append('&');
|
||||
}
|
||||
var query = queryBuilder.ToString().TrimEnd('&');
|
||||
|
||||
var url = $"{Utils.UrlEncode(realm.Token)}@{GetIpv6(realm.RendezvousHost)}:{realm.RendezvousPort}";
|
||||
var scheme = realm.IsHttp ? Global.Hysteria2HttpRealmProtocolShare : Global.Hysteria2RealmProtocolShare;
|
||||
return $"{scheme}{url}/{realm.RealmName}{query}{remark}";
|
||||
}
|
||||
|
||||
private static void ResolveHy2UriQuery(NameValueCollection query, ref ProfileItem item)
|
||||
{
|
||||
if (item.CertSha.IsNullOrEmpty())
|
||||
{
|
||||
item.CertSha = GetQueryDecoded(query, "pinSHA256");
|
||||
}
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||
{
|
||||
Ports = GetQueryDecoded(query, "mport"),
|
||||
SalamanderPass = GetQueryDecoded(query, "obfs-password"),
|
||||
// NOTE: The "PacketSize" parameter is not defined by the official URI Scheme, may remove or rename it in the future.
|
||||
GeckoMinPacketSize = GetQueryDecoded(query, "minPacketSize"),
|
||||
GeckoMaxPacketSize = GetQueryDecoded(query, "maxPacketSize"),
|
||||
});
|
||||
if (GetQueryDecoded(query, "obfs") == "gecko")
|
||||
{
|
||||
// Ensure the "PacketSize" parameters are present for gecko obfs.
|
||||
var protocolExtraItem = item.GetProtocolExtra();
|
||||
if (protocolExtraItem.GeckoMinPacketSize.IsNullOrEmpty())
|
||||
{
|
||||
protocolExtraItem = protocolExtraItem with
|
||||
{
|
||||
GeckoMinPacketSize = "512",
|
||||
};
|
||||
}
|
||||
if (protocolExtraItem.GeckoMaxPacketSize.IsNullOrEmpty())
|
||||
{
|
||||
protocolExtraItem = protocolExtraItem with
|
||||
{
|
||||
GeckoMaxPacketSize = "1200",
|
||||
};
|
||||
}
|
||||
item.SetProtocolExtra(protocolExtraItem);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ToHy2UriQuery(ProfileItem item, ref Dictionary<string, string> dicQuery)
|
||||
{
|
||||
if (!item.CertSha.IsNullOrEmpty()
|
||||
&& !item.CertSha.Contains(','))
|
||||
{
|
||||
var sha = item.CertSha;
|
||||
dicQuery.Add("pinSHA256", Utils.UrlEncode(sha));
|
||||
}
|
||||
var protocolExtraItem = item.GetProtocolExtra();
|
||||
var isGecko = !protocolExtraItem.GeckoMinPacketSize.IsNullOrEmpty() || !protocolExtraItem.GeckoMaxPacketSize.IsNullOrEmpty();
|
||||
if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty())
|
||||
{
|
||||
dicQuery.Add("obfs", isGecko ? "gecko" : "salamander");
|
||||
dicQuery.Add("obfs-password", Utils.UrlEncode(protocolExtraItem.SalamanderPass));
|
||||
if (isGecko)
|
||||
{
|
||||
// NOTE: The "PacketSize" parameter is not defined by the official URI Scheme, may remove or rename it in the future.
|
||||
dicQuery.Add("minPacketSize", protocolExtraItem.GeckoMinPacketSize);
|
||||
dicQuery.Add("maxPacketSize", protocolExtraItem.GeckoMaxPacketSize);
|
||||
}
|
||||
}
|
||||
if (!protocolExtraItem.Ports.IsNullOrEmpty())
|
||||
{
|
||||
dicQuery.Add("mport", Utils.UrlEncode(protocolExtraItem.Ports.Replace(':', '-')));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,9 +244,26 @@ public sealed class AppManager
|
||||
{
|
||||
return [];
|
||||
}
|
||||
return await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||
.Where(it => ids.Contains(it.IndexId))
|
||||
.ToListAsync();
|
||||
|
||||
if (ids.Count <= Global.SqliteMaxBatchSize)
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||
.Where(it => ids.Contains(it.IndexId))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
var items = new List<ProfileItem>();
|
||||
for (var size = 0; size < ids.Count; size += Global.SqliteMaxBatchSize)
|
||||
{
|
||||
var chunk = ids.Skip(size).Take(Global.SqliteMaxBatchSize).ToList();
|
||||
var chunkItems = await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||
.Where(it => chunk.Contains(it.IndexId))
|
||||
.ToListAsync();
|
||||
|
||||
items.AddRange(chunkItems);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, ProfileItem>> GetProfileItemsByIndexIdsAsMap(IEnumerable<string> indexIds)
|
||||
@@ -257,18 +274,11 @@ public sealed class AppManager
|
||||
|
||||
public async Task<List<ProfileItem>> GetProfileItemsOrderedByIndexIds(IEnumerable<string> indexIds)
|
||||
{
|
||||
var idList = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
|
||||
if (idList.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var items = await SQLiteHelper.Instance.TableAsync<ProfileItem>()
|
||||
.Where(it => idList.Contains(it.IndexId))
|
||||
.ToListAsync();
|
||||
var ids = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
|
||||
var items = await GetProfileItemsByIndexIds(ids);
|
||||
var itemMap = items.ToDictionary(it => it.IndexId);
|
||||
|
||||
return idList.Select(itemMap.GetValueOrDefault)
|
||||
return ids.Select(itemMap.GetValueOrDefault)
|
||||
.Where(item => item != null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ public class CoreManager
|
||||
}
|
||||
|
||||
await CoreStart(mainContext);
|
||||
await WaitForProxyPort(preContext);
|
||||
await CoreStartPreService(preContext);
|
||||
|
||||
AppManager.Instance.RunningCoreType = preContext?.RunCoreType ?? mainContext.RunCoreType;
|
||||
@@ -213,6 +214,75 @@ public class CoreManager
|
||||
await _updateFunc?.Invoke(notify, msg);
|
||||
}
|
||||
|
||||
private static async Task WaitForProxyPort(CoreConfigContext? preContext, int timeoutMs = 5000)
|
||||
{
|
||||
if (preContext is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!preContext.AppConfig.TunModeItem.EnableTun)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var rootCts = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeoutMs));
|
||||
var rootToken = rootCts.Token;
|
||||
|
||||
var port = preContext.Node.Port;
|
||||
// SOCKS5 client greeting: VER=5, NMETHODS=1, METHOD=0x00 (no auth)
|
||||
ReadOnlyMemory<byte> greeting = new byte[] { 0x05, 0x01, 0x00 };
|
||||
var buf = new byte[2];
|
||||
|
||||
while (!rootToken.IsCancellationRequested)
|
||||
{
|
||||
using var tcp = new TcpClient();
|
||||
using var attemptCts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50));
|
||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(rootToken, attemptCts.Token);
|
||||
var linkedToken = linkedCts.Token;
|
||||
try
|
||||
{
|
||||
await tcp.ConnectAsync(Global.Loopback, port, linkedToken);
|
||||
var stream = tcp.GetStream();
|
||||
|
||||
await stream.WriteAsync(greeting, linkedToken);
|
||||
|
||||
var read = await stream.ReadAsync(buf.AsMemory(0, 2), linkedToken);
|
||||
|
||||
// Server selection: VER=5, METHOD=0x00 — proxy is fully ready
|
||||
if (read == 2 && buf[0] == 0x05)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (!rootToken.IsCancellationRequested)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Logging.SaveLog($"WaitForProxyPort Timeout waiting for proxy port {port} to be ready.");
|
||||
return;
|
||||
}
|
||||
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionRefused)
|
||||
{
|
||||
// Connection refused, proxy not ready yet, wait 50ms before retrying
|
||||
try
|
||||
{
|
||||
await Task.Delay(50, rootToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logging.SaveLog($"WaitForProxyPort Timeout waiting for proxy port {port} to be ready.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore other exceptions and continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private
|
||||
|
||||
#region Process
|
||||
@@ -230,7 +300,7 @@ public class CoreManager
|
||||
{
|
||||
if (mayNeedSudo
|
||||
&& _config.TunModeItem.EnableTun
|
||||
&& (coreInfo.CoreType is ECoreType.sing_box or ECoreType.mihomo)
|
||||
&& (coreInfo.CoreType is ECoreType.sing_box or ECoreType.mihomo or ECoreType.Xray)
|
||||
&& Utils.IsNonWindows())
|
||||
{
|
||||
_linuxSudo = true;
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
using NetBridgeLib.Services;
|
||||
|
||||
namespace ServiceLib.Manager;
|
||||
|
||||
public sealed class NetBridgeManager
|
||||
{
|
||||
private static readonly Lazy<NetBridgeManager> _instance = new(() => new());
|
||||
public static NetBridgeManager Instance => _instance.Value;
|
||||
private readonly Config _config = AppManager.Instance.Config;
|
||||
private NetBridgeService? _netBridgeService;
|
||||
private bool _isProxyRunning;
|
||||
private bool _isInitialized;
|
||||
private List<NetBridgeRuleConfig> _ruleConfigs = [];
|
||||
private Func<bool, string, Task>? _updateFunc;
|
||||
private uint _proxyConfigId;
|
||||
|
||||
public async Task Init(Func<bool, string, Task>? updateFunc = null)
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_updateFunc = updateFunc;
|
||||
|
||||
try
|
||||
{
|
||||
_netBridgeService = new NetBridgeService();
|
||||
_netBridgeService.LogReceived += msg =>
|
||||
{
|
||||
var message = $"NetBridge Log: {msg}";
|
||||
_ = _updateFunc?.Invoke(false, message);
|
||||
};
|
||||
|
||||
_netBridgeService.ConnectionReceived += (processName, pid, destIp, destPort, proxyInfo) =>
|
||||
{
|
||||
var message = $"NetBridge Connection: {processName} (PID: {pid}) -> {destIp}:{destPort} -> {proxyInfo}";
|
||||
_ = _updateFunc?.Invoke(false, message);
|
||||
};
|
||||
|
||||
_ruleConfigs = BuildRuleConfigs(_config.NetBridgeItem?.RuleProcess);
|
||||
_isInitialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var error = $"Failed to initialize NetBridgeService: {ex.Message}";
|
||||
await _updateFunc?.Invoke(true, error);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Start()
|
||||
{
|
||||
if (_isProxyRunning)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_netBridgeService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var started = _netBridgeService.Start();
|
||||
if (!started)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_isProxyRunning = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var error = $"Failed to start NetBridgeService: {ex.Message}";
|
||||
await _updateFunc?.Invoke(true, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> Stop()
|
||||
{
|
||||
if (!_isProxyRunning)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_netBridgeService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var stopped = _netBridgeService.Stop();
|
||||
if (!stopped)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_isProxyRunning = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var error = $"Failed to stop NetBridgeService: {ex.Message}";
|
||||
await _updateFunc?.Invoke(true, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateRoutes(string? ruleProcess)
|
||||
{
|
||||
var newRuleConfigs = BuildRuleConfigs(ruleProcess);
|
||||
|
||||
_ruleConfigs = newRuleConfigs;
|
||||
|
||||
if (!_isProxyRunning)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return await ApplyRoutesInternal();
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateProxyConfig(string proxyHost, int proxyPort)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_netBridgeService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var proxyType = "SOCKS5";
|
||||
var username = "";
|
||||
var password = "";
|
||||
|
||||
if (_proxyConfigId > 0)
|
||||
{
|
||||
var edited = _netBridgeService.EditProxyConfig(_proxyConfigId, proxyType, proxyHost, (ushort)proxyPort, username, password);
|
||||
if (!edited)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_proxyConfigId = _netBridgeService.AddProxyConfig(proxyType, proxyHost, (ushort)proxyPort, username, password);
|
||||
if (_proxyConfigId == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var error = $"Failed to update proxy config: {ex.Message}";
|
||||
await _updateFunc?.Invoke(true, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SetDnsViaProxy(bool enable)
|
||||
{
|
||||
if (_netBridgeService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_netBridgeService.SetDnsViaProxy(enable);
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private async Task<bool> ApplyRoutesInternal()
|
||||
{
|
||||
if (_netBridgeService == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
List<NetBridgeRuleConfig> rules;
|
||||
|
||||
rules = _ruleConfigs.Select(JsonUtils.DeepCopy).ToList();
|
||||
|
||||
foreach (var rule in rules.Where(x => x.RuleId > 0))
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = _netBridgeService.DeleteRule(rule.RuleId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < rules.Count; i++)
|
||||
{
|
||||
var rule = rules[i];
|
||||
var newRuleId = _netBridgeService.AddRule(rule.ProcessName, rule.TargetHosts, rule.TargetPorts, rule.Protocol, rule.Action, rule.ProxyConfigId);
|
||||
if (newRuleId == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
rules[i].RuleId = newRuleId;
|
||||
}
|
||||
|
||||
_ruleConfigs = rules;
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private static List<NetBridgeRuleConfig> BuildRuleConfigs(string? ruleProcess)
|
||||
{
|
||||
if (ruleProcess.IsNullOrEmpty())
|
||||
{
|
||||
return new();
|
||||
}
|
||||
|
||||
var processNames = Utils.String2List(Utils.Convert2Comma(ruleProcess));
|
||||
return processNames.Select(processName => new NetBridgeRuleConfig
|
||||
{
|
||||
ProcessName = processName,
|
||||
TargetHosts = "*",
|
||||
TargetPorts = "*",
|
||||
Protocol = "BOTH",
|
||||
Action = "PROXY",
|
||||
ProxyConfigId = 0
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ public class Config
|
||||
public SystemProxyItem SystemProxyItem { get; set; }
|
||||
public WebDavItem WebDavItem { get; set; }
|
||||
public CheckUpdateItem CheckUpdateItem { get; set; }
|
||||
public NetBridgeItem NetBridgeItem { get; set; }
|
||||
public Fragment4RayItem? Fragment4RayItem { get; set; }
|
||||
public List<InItem> Inbound { get; set; }
|
||||
public List<KeyEventItem> GlobalHotkeys { get; set; }
|
||||
|
||||
@@ -249,6 +249,7 @@ public class Fragment4RayItem
|
||||
public string? Packets { get; set; }
|
||||
public string? Length { get; set; }
|
||||
public string? Interval { get; set; }
|
||||
public string? MaxSplit { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -277,10 +278,3 @@ public class SimpleDNSItem
|
||||
public string? Hosts { get; set; }
|
||||
public string? DirectExpectedIPs { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class NetBridgeItem
|
||||
{
|
||||
public string? RuleProcess { get; set; }
|
||||
public bool EnableDnsViaProxy { get; set; }
|
||||
}
|
||||
|
||||
@@ -248,6 +248,16 @@ public class HyObfs4Sbox
|
||||
{
|
||||
public string? type { get; set; }
|
||||
public string? password { get; set; }
|
||||
public int? min_packet_size { get; set; }
|
||||
public int? max_packet_size { get; set; }
|
||||
}
|
||||
|
||||
public class HyRealm4Sbox
|
||||
{
|
||||
public string? server_url { get; set; }
|
||||
public string? token { get; set; }
|
||||
public string? realm_id { get; set; }
|
||||
public List<string>? stun_servers { get; set; }
|
||||
}
|
||||
|
||||
public class Server4Sbox : BaseServer4Sbox
|
||||
@@ -334,6 +344,7 @@ public abstract class DialFields4Sbox
|
||||
public Multiplex4Sbox? multiplex { get; set; }
|
||||
public Transport4Sbox? transport { get; set; }
|
||||
public HyObfs4Sbox? obfs { get; set; }
|
||||
public HyRealm4Sbox? realm { get; set; }
|
||||
}
|
||||
|
||||
public abstract class BaseServer4Sbox : DialFields4Sbox
|
||||
|
||||
@@ -289,16 +289,7 @@ public class BalancersStrategySettings4Ray
|
||||
{
|
||||
public int? expected { get; set; }
|
||||
public string? maxRTT { get; set; }
|
||||
public float? tolerance { get; set; }
|
||||
public List<string>? baselines { get; set; }
|
||||
public List<BalancersStrategySettingsCosts4Ray>? costs { get; set; }
|
||||
}
|
||||
|
||||
public class BalancersStrategySettingsCosts4Ray
|
||||
{
|
||||
public bool? regexp { get; set; }
|
||||
public string? match { get; set; }
|
||||
public float? value { get; set; }
|
||||
public double? tolerance { get; set; }
|
||||
}
|
||||
|
||||
public class Observatory4Ray
|
||||
@@ -507,11 +498,16 @@ public class MaskSettings4Ray
|
||||
|
||||
public string? password { get; set; }
|
||||
|
||||
public string? url { get; set; }
|
||||
public List<string>? stunServers { get; set; }
|
||||
public string? packetSize { get; set; }
|
||||
|
||||
// fragment
|
||||
public string? packets { get; set; }
|
||||
|
||||
public string? length { get; set; }
|
||||
public string? delay { get; set; }
|
||||
public int? maxSplit { get; set; }
|
||||
|
||||
// noise
|
||||
public int? reset { get; set; }
|
||||
|
||||
115
v2rayN/ServiceLib/Models/Dto/HyRealm.cs
Normal file
115
v2rayN/ServiceLib/Models/Dto/HyRealm.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
namespace ServiceLib.Models.Dto;
|
||||
|
||||
// realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]&stun=<stun-host>[:port]...
|
||||
// realm+http://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]&stun=<stun-host>[:port]...
|
||||
// example:
|
||||
// realm://public@realm.hy2.io/57f9be7c-2810-4f5b-8cb9-260bc84d6c90?stun=example.stun:3478&stun=example2.stun:3478
|
||||
public record HyRealm(
|
||||
bool IsHttp,
|
||||
string Token,
|
||||
string RendezvousHost,
|
||||
int RendezvousPort,
|
||||
string RealmName,
|
||||
List<string> StunList
|
||||
)
|
||||
{
|
||||
public string RendezvousHostPort => RendezvousPort > 0 ? $"{RendezvousHost}:{RendezvousPort}" : RendezvousHost;
|
||||
|
||||
/// <summary>
|
||||
/// sing-box realm.server_url requires a scheme (https:// or http://).
|
||||
/// </summary>
|
||||
public string ToServerUrl()
|
||||
{
|
||||
var scheme = IsHttp ? "http" : "https";
|
||||
return $"{scheme}://{RendezvousHostPort}";
|
||||
}
|
||||
|
||||
public static bool TryParse(string? str, out HyRealm? realm)
|
||||
{
|
||||
realm = null;
|
||||
if (str == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
var isHttp = str.StartsWith("realm+http://");
|
||||
var prefix = isHttp ? "realm+http://" : "realm://";
|
||||
if (!str.StartsWith(prefix))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var uri = new Uri(str);
|
||||
var token = Utils.UrlDecode(uri.UserInfo);
|
||||
var rendezvousHost = uri.Host;
|
||||
var rendezvousPort = uri.IsDefaultPort ? (isHttp ? 80 : 443) : uri.Port;
|
||||
var realmName = uri.AbsolutePath.TrimStart('/');
|
||||
var stunList = new List<string>();
|
||||
var query = uri.Query;
|
||||
if (!query.IsNullOrEmpty())
|
||||
{
|
||||
if (query.StartsWith('?'))
|
||||
{
|
||||
query = query.Substring(1);
|
||||
}
|
||||
var pairs = query.Split('&', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
var idx = pair.IndexOf('=');
|
||||
if (idx <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var key = pair.Substring(0, idx);
|
||||
if (!key.Equals("stun", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var val = pair.Substring(idx + 1);
|
||||
stunList.Add(Utils.UrlDecode(val));
|
||||
}
|
||||
}
|
||||
realm = new HyRealm(isHttp, token, rendezvousHost, rendezvousPort, realmName, stunList);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string ToUri()
|
||||
{
|
||||
var prefix = IsHttp ? "realm+http://" : "realm://";
|
||||
var uriBuilder = new UriBuilder
|
||||
{
|
||||
Scheme = IsHttp ? "http" : "realm",
|
||||
Host = RendezvousHost,
|
||||
Port = RendezvousPort,
|
||||
Path = "/" + RealmName,
|
||||
UserName = Utils.UrlEncode(Token),
|
||||
};
|
||||
if (StunList is { Count: > 0 })
|
||||
{
|
||||
// var query = string.Join('&', StunList.Select(s => "stun=" + Utils.UrlEncode(s)));
|
||||
// NOTE: maybe we don't need to encode the stun host:port, since it should be a valid URI component already, and encoding will make it unreadable
|
||||
var query = string.Join('&', StunList.Select(s => "stun=" + s));
|
||||
uriBuilder.Query = query;
|
||||
}
|
||||
return prefix + uriBuilder.Uri.GetComponents(
|
||||
UriComponents.UserInfo | UriComponents.HostAndPort | UriComponents.PathAndQuery, UriFormat.UriEscaped);
|
||||
}
|
||||
|
||||
public string ToUriForFinalmask()
|
||||
{
|
||||
var prefix = IsHttp ? "realm+http://" : "realm://";
|
||||
var uriBuilder = new StringBuilder();
|
||||
uriBuilder.Append(prefix);
|
||||
uriBuilder.Append(Utils.UrlEncode(Token));
|
||||
uriBuilder.Append('@');
|
||||
uriBuilder.Append(RendezvousHostPort);
|
||||
uriBuilder.Append('/');
|
||||
uriBuilder.Append(Utils.UrlEncode(RealmName));
|
||||
return uriBuilder.ToString();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace ServiceLib.Models.Dto;
|
||||
|
||||
public sealed class NetBridgeRuleConfig
|
||||
{
|
||||
public uint RuleId { get; set; }
|
||||
public string ProcessName { get; set; }
|
||||
public string TargetHosts { get; set; }
|
||||
public string TargetPorts { get; set; }
|
||||
public string Protocol { get; set; }
|
||||
public string Action { get; set; }
|
||||
public uint ProxyConfigId { get; set; }
|
||||
}
|
||||
@@ -31,6 +31,12 @@ public record ProtocolExtraItem
|
||||
public int? DownMbps { get; init; }
|
||||
public string? Ports { get; init; }
|
||||
public string? HopInterval { get; init; }
|
||||
// realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]&stun=<stun-host>[:port]...
|
||||
// example:
|
||||
// realm://public@realm.hy2.io/57f9be7c-2810-4f5b-8cb9-260bc84d6c90?stun=example.stun:3478&stun=example2.stun:3478
|
||||
public string? Hy2RealmUrl { get; init; }
|
||||
public string? GeckoMinPacketSize { get; init; }
|
||||
public string? GeckoMaxPacketSize { get; init; }
|
||||
|
||||
// naiveproxy
|
||||
public int? InsecureConcurrency { get; init; }
|
||||
|
||||
191
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
191
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -222,15 +222,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please fill in the correct IPv4 address for SendThrough. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string FillCorrectSendThroughIPv4 {
|
||||
get {
|
||||
return ResourceManager.GetString("FillCorrectSendThroughIPv4", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please enter the correct port format. 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -240,6 +231,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Invalid range format. Use 'from-to' (e.g., 50-100). 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string FillFragmentParameterError {
|
||||
get {
|
||||
return ResourceManager.GetString("FillFragmentParameterError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please enter the local listening port. 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -321,6 +321,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Invalid Realm URL. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string InvalidHy2RealmUrl {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidHy2RealmUrl", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Invalid address (URL) 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -1320,15 +1329,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Process traffic hijacking (experimental) 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuNetBridge {
|
||||
get {
|
||||
return ResourceManager.GetString("menuNetBridge", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 New Update 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2076,6 +2076,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Insecure configuration detected: AllowInsecure is enabled but no certificate is provided. This may cause MITM attacks. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgInsecureConfiguration {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgInsecureConfiguration", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 The {0} property is invalid, please check 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2310,15 +2319,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 The process names that need to be proxied; separate multiple processes with commas. Processes not in the list will also be hijacked, but will connect directly. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string NetBridgeRuleTips {
|
||||
get {
|
||||
return ResourceManager.GetString("NetBridgeRuleTips", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Non-VMess or SS protocol 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3051,15 +3051,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Enables process traffic hijacking (experimental), similar to Proxifer; can coexist with system proxy; conflicts with TUN mode, please do not enable them simultaneously;please allow v2rayN to use a private network in your firewall. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbEnableNetBridge {
|
||||
get {
|
||||
return ResourceManager.GetString("TbEnableNetBridge", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 DNS via Bridge 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3177,6 +3168,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Gecko Packet Size (min/max) 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbGeckoPacketSize {
|
||||
get {
|
||||
return ResourceManager.GetString("TbGeckoPacketSize", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Global Hotkey Settings 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3240,6 +3240,24 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Realm URL 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbHy2RealmUrl {
|
||||
get {
|
||||
return ResourceManager.GetString("TbHy2RealmUrl", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Format: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port] 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbHy2RealmUrlTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbHy2RealmUrlTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 ICMP routing policy 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3708,15 +3726,6 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Save rules 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSaveNetBridgeRule {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSaveNetBridgeRule", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 sing-box Full Config Template 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3853,7 +3862,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems or TUN mode 的本地化字符串。
|
||||
/// 查找类似 For multi-interface environments, enter the interface name for outbound connections. On Linux/macOS, it only works when TUN mode is enabled. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsBindInterfaceTip {
|
||||
get {
|
||||
@@ -4140,6 +4149,96 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fallback Delay 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentFallbackDelay {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentFallbackDelay", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fallback delay when ACK detection unavailable (e.g., 500ms). sing-box only. Empty = default. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentFallbackDelayTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentFallbackDelayTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fragment Interval 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentInterval {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentInterval", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Delay between fragments in ms (e.g., 10-20). Range 1-100. First value must be <= second. Empty = default. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentIntervalTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentIntervalTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fragment Length 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentLength {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentLength", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fragment size range in bytes (e.g., 50-100). First value must be <= second. Empty = default. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentLengthTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentLengthTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Max Split 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentMaxSplit {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentMaxSplit", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Maximum number of fragments per packet (0-10000, 0 = unlimited). Xray only. Empty = default. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentMaxSplitTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentMaxSplitTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fragment Packets 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentPackets {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentPackets", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Packets to fragment: tlshello (TLS ClientHello) or 1-1 to 1-5 (first N TCP packets) 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbSettingsFragmentPacketsTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbSettingsFragmentPacketsTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Geo files source (optional) 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -1104,6 +1104,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>فعال کردن فرگمنت</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>پکتهای فرگمنت</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>پکتهای قابل فرگمنت: tlshello (TLS ClientHello) یا 1-1 تا 1-5 (N اول پکت TCP)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>طول فرگمنت</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>محدوده اندازه فرگمنت بایت (مثلا 50-100). اول ≤ دوم. خالی = پیشفرض.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>فاصله فرگمنت</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>تأخیر بین فرگمنتها میلیثانیه (مثلا 10-20). بازه 1-100. اول ≤ دوم. خالی = پیشفرض.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>حداکثر تقسیم</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>مکسیمم تعداد تقسیم (0 = نامحدود، 0-10000). فقط Xray-core. خالی = پیشفرض.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>تأخیر فولبک</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>تأخیر فولبک فرگمنت TLS. فقط sing-box. خالی = پیشفرض.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>فرمت محدوده نامعتبر. از 'from-to' استفاده کنید (مثلا 50-100).</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>فعال کردن کش فایل مجموعه قوانین برای sing-box</value>
|
||||
</data>
|
||||
@@ -1710,9 +1743,6 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>For multi-interface environments, enter the local machine's IPv4 address</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>Please fill in the correct IPv4 address for SendThrough.</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>Bind Interface</value>
|
||||
</data>
|
||||
@@ -1740,4 +1770,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="menuNewUpdate" xml:space="preserve">
|
||||
<value>New Update</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
@@ -1101,6 +1101,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Activer le fragmentation (Fragment)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Paquets fragmentés</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>Paquets à fragmenter : tlshello (TLS ClientHello) ou 1-1 à 1-5 (premiers N paquets TCP)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Longueur fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>Plage de taille fragment (ex: 50-100). Première valeur ≤ seconde. Vide = défaut.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Intervalle fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>Délai entre fragments en ms (ex: 10-20). Plage 1-100. Première valeur ≤ seconde. Vide = défaut.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>Max Split</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>Nb max de splits (0 = illimité, 0-10000). Xray-core seulement. Vide = défaut.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>Délai fallback</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>Délai fallback pour fragment TLS. sing-box seulement. Vide = défaut.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>Format de plage invalide. Utilisez 'from-to' (ex: 50-100).</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Activer le fichier de cache de sing-box (fichiers règles)</value>
|
||||
</data>
|
||||
@@ -1701,9 +1734,6 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>Pour environnements multi-interfaces, entrez l'adresse IPv4 de la machine locale</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>Veuillez saisir l’adresse IPv4 correcte de SendThrough.</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>Lier l'interface</value>
|
||||
</data>
|
||||
|
||||
@@ -1104,6 +1104,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Fragment engedélyezése</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Fragmentált csomagok</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>Fragmentálandó csomagok: tlshello (TLS ClientHello) vagy 1-1 – 1-5 (az első N TCP csomag)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Fragment hossz</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>Fragment méret tartománya bájtokban (pl. 50-100). Az első érték ≤ második. Üres = alapértelmezett.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Fragment intervallum</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>Késleltetés a fragmented csomagok között ms-ben (pl. 10-20). Tartomány 1-100. Az első érték ≤ második. Üres = alapértelmezett.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>Max darabolás</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>Maximális darabolások száma (0 = korlátlan, 0-10000). Csak Xray-core. Üres = alapértelmezett.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>Fallback késleltetés</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>TLS fragment fallback késleltetés. Csak sing-box. Üres = alapértelmezett.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>Érvénytelen tartomány formátum. Használja 'from-to' (pl. 50-100).</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Gyorsítótár fájl engedélyezése sing-boxhoz (szabálykészlet fájlok)</value>
|
||||
</data>
|
||||
@@ -1710,9 +1743,6 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>For multi-interface environments, enter the local machine's IPv4 address</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>Please fill in the correct IPv4 address for SendThrough.</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>Bind Interface</value>
|
||||
</data>
|
||||
|
||||
@@ -1104,6 +1104,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Aktifkan fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Paket Fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>Paket untuk difragmentasikan: tlshello (TLS ClientHello) atau 1-1 s.d. 1-5 (N paket TCP pertama)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Panjang Fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>Rentang ukuran fragment (byte), cth: 50-100. Nilai pertama harus ≤ kedua. Kosong = default.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Interval Fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>Jeda antar fragment (ms), cth: 10-20. Rentang 1-100. Nilai pertama harus ≤ kedua. Kosong = default.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>Maks Split</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>Maks jumlah split (0 = unlimited, 0-10000). Hanya Xray-core. Kosong = default.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>Fallback Delay</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>Delay fallback untuk TLS fragment. Hanya sing-box. Kosong = default.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>Format range tidak valid. Gunakan 'from-to' (cth: 50-100).</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Aktifkan file cache untuk sing-box (file ruleset)</value>
|
||||
</data>
|
||||
@@ -1701,12 +1734,6 @@ Tindakan "Ambil sertifikat" mungkin gagal jika sertifikat self-signed digunakan
|
||||
<data name="TransportExtra" xml:space="preserve">
|
||||
<value>XHTTP Extra</value>
|
||||
</data>
|
||||
<data name="TbAllowInsecureCertFetch" xml:space="preserve">
|
||||
<value>Izinkan pengambilan sertifikat tidak aman (self-signed)</value>
|
||||
</data>
|
||||
<data name="TbAllowInsecureCertFetchTips" xml:space="preserve">
|
||||
<value>Hanya untuk mengambil sertifikat self-signed. Fitur ini dapat mengekspos Anda pada risiko MITM.</value>
|
||||
</data>
|
||||
<data name="menuUdpTestServer" xml:space="preserve">
|
||||
<value>Uji latensi UDP konfigurasi</value>
|
||||
</data>
|
||||
@@ -1719,9 +1746,6 @@ Tindakan "Ambil sertifikat" mungkin gagal jika sertifikat self-signed digunakan
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>Untuk lingkungan multi-antarmuka, masukkan alamat IPv4 mesin lokal.</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>Isi alamat IPv4 yang benar untuk SendThrough.</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>Bind Interface</value>
|
||||
</data>
|
||||
|
||||
@@ -1104,6 +1104,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Enable fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Fragment Packets</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>Packets to fragment: tlshello (TLS ClientHello) or 1-1 to 1-5 (first N TCP packets)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Fragment Length</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>Fragment size range in bytes (e.g., 50-100). First value must be <= second. Empty = default.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Fragment Interval</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>Delay between fragments in ms (e.g., 10-20). Range 1-100. First value must be <= second. Empty = default.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>Max Split</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>Maximum number of fragments per packet (0-10000, 0 = unlimited). Xray only. Empty = default.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>Fallback Delay</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>Fallback delay when ACK detection unavailable (e.g., 500ms). sing-box only. Empty = default.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>Invalid range format. Use 'from-to' (e.g., 50-100).</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Enable cache file for sing-box (ruleset files)</value>
|
||||
</data>
|
||||
@@ -1713,14 +1746,11 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>For multi-interface environments, enter the local machine's IPv4 address</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>Please fill in the correct IPv4 address for SendThrough.</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>Bind Interface</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
|
||||
<value>For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems or TUN mode</value>
|
||||
<value>For multi-interface environments, enter the interface name for outbound connections. On Linux/macOS, it only works when TUN mode is enabled.</value>
|
||||
</data>
|
||||
<data name="TbPreSharedKey" xml:space="preserve">
|
||||
<value>PreSharedKey</value>
|
||||
@@ -1764,19 +1794,22 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||
<value>Split the tail of packets into smaller fragments. This may affect throughput and latency.</value>
|
||||
</data>
|
||||
<data name="TbEnableNetBridge" xml:space="preserve">
|
||||
<value>Enables process traffic hijacking (experimental), similar to Proxifer; can coexist with system proxy; conflicts with TUN mode, please do not enable them simultaneously;please allow v2rayN to use a private network in your firewall.</value>
|
||||
</data>
|
||||
<data name="NetBridgeRuleTips" xml:space="preserve">
|
||||
<value>The process names that need to be proxied; separate multiple processes with commas. Processes not in the list will also be hijacked, but will connect directly.</value>
|
||||
</data>
|
||||
<data name="menuNetBridge" xml:space="preserve">
|
||||
<value>Process traffic hijacking (experimental)</value>
|
||||
</data>
|
||||
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||
<value>DNS via Bridge</value>
|
||||
</data>
|
||||
<data name="TbSaveNetBridgeRule" xml:space="preserve">
|
||||
<value>Save rules</value>
|
||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||
<value>Insecure configuration detected: AllowInsecure is enabled but no certificate is provided. This may cause MITM attacks.</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrl" xml:space="preserve">
|
||||
<value>Realm URL</value>
|
||||
</data>
|
||||
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||
<value>Invalid Realm URL.</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||
<value>Format: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||
</data>
|
||||
<data name="TbGeckoPacketSize" xml:space="preserve">
|
||||
<value>Gecko Packet Size (min/max)</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1104,6 +1104,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Включить фрагментацию (Fragment)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Пакеты для фрагментации</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>Пакеты для фрагментации: tlshello (TLS ClientHello) или 1-1 — 1-5 (первые N TCP-пакетов)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Длина фрагмента</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>Диапазон размера фрагмента в байтах (например, 50-100). Первое значение не больше второго. Пусто = по умолчанию.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Интервал фрагментации</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>Задержка между фрагментами в мс (например, 10-20). Диапазон 1-100. Первое значение не больше второго. Пусто = по умолчанию.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>Макс. разделение</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>Максимальное число разделений (0 = без лимита, 0-10000). Только для Xray-core. Пусто = по умолчанию.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>Резервная задержка</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>Резервная задержка для TLS-фрагмента. Только для sing-box. Пусто = по умолчанию.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>Неверный формат диапазона. Используйте 'from-to' (например, 50-100).</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Включить файл кэша для sing-box (файлы наборов правил)</value>
|
||||
</data>
|
||||
@@ -1710,14 +1743,11 @@
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>Для среды с несколькими сетевыми интерфейсами укажите IPv4-адрес локального компьютера</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>Укажите корректный IPv4-адрес для SendThrough.</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>Привязать интерфейс</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterfaceTip" xml:space="preserve">
|
||||
<value>Для среды с несколькими сетевыми интерфейсами укажите имя интерфейса для привязки. Работает только в Windows и режиме TUN</value>
|
||||
<value>Для среды с несколькими сетевыми интерфейсами укажите имя интерфейса для исходящих подключений. На Linux/macOS работает только при включённом TUN.</value>
|
||||
</data>
|
||||
<data name="TbPreSharedKey" xml:space="preserve">
|
||||
<value>Общий ключ (PSK)</value>
|
||||
|
||||
@@ -1030,7 +1030,7 @@
|
||||
<value>添加 [Hysteria2]</value>
|
||||
</data>
|
||||
<data name="TbSettingsHysteriaBandwidth" xml:space="preserve">
|
||||
<value>Hysteria 最大带宽 (Up/Dw)</value>
|
||||
<value>Hysteria 最大带宽 (上传/下载)</value>
|
||||
</data>
|
||||
<data name="TbSettingsUseSystemHosts" xml:space="preserve">
|
||||
<value>使用系统 hosts</value>
|
||||
@@ -1101,6 +1101,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>启用分片 (Fragment)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>分片包类型</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>要分片的包类型: tlshello (TLS ClientHello) 或 1-1 到 1-5 (前 N 个 TCP 包)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>分片长度</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>分片大小范围(字节), 如 50-100. 第一个值必须 <= 第二个值. 留空使用默认值.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>分片间隔</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>分片间延迟(毫秒), 如 10-20. 范围 1-100. 第一个值必须 <= 第二个值. 留空使用默认值.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>最大分片数</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>每个包最大分片数 (0-10000, 0=无限制). 仅 Xray. 留空使用默认值.</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>回退延迟</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>ACK 检测不可用时的回退延迟 (如 500ms). 仅 sing-box. 留空使用默认值.</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>无效的范围格式。请使用 'from-to' (如 50-100)。</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>启用 sing-box (规则集文件) 的缓存文件</value>
|
||||
</data>
|
||||
@@ -1707,9 +1740,6 @@
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>用于多网口环境,请填写本机 IPv4 地址</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>请填写正确的 SendThrough IPv4 地址。</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>绑定网口</value>
|
||||
</data>
|
||||
@@ -1761,19 +1791,22 @@
|
||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||
<value>将数据包末端拆分为更小片段发送,可能影响吞吐与延迟。</value>
|
||||
</data>
|
||||
<data name="TbEnableNetBridge" xml:space="preserve">
|
||||
<value>启用进程流量劫持(实验性),类似 Proxifer 功能;可以和系统代理并存;和 TUN 模式冲突,请不要同时开启;请在防火墙中允许 v2rayN 使用专用网络</value>
|
||||
</data>
|
||||
<data name="NetBridgeRuleTips" xml:space="preserve">
|
||||
<value>需要代理的进程名,多个进程请用逗号分隔;不在列表中的进程也会被劫持,但是会直连</value>
|
||||
</data>
|
||||
<data name="menuNetBridge" xml:space="preserve">
|
||||
<value>进程流量劫持(实验性)</value>
|
||||
</data>
|
||||
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||
<value>DNS 通过 Bridge</value>
|
||||
</data>
|
||||
<data name="TbSaveNetBridgeRule" xml:space="preserve">
|
||||
<value>保存规则</value>
|
||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||
<value>检测到不安全配置:AllowInsecure 已启用,但未提供证书。这可能会导致中间人攻击。</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrl" xml:space="preserve">
|
||||
<value>Realm URL</value>
|
||||
</data>
|
||||
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||
<value>Realm URL 不正确。</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||
<value>格式:realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||
</data>
|
||||
<data name="TbGeckoPacketSize" xml:space="preserve">
|
||||
<value>Gecko 包大小 (最小/最大)</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1101,6 +1101,39 @@
|
||||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>啟用分片(Fragment)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>分片封包類型</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPacketsTip" xml:space="preserve">
|
||||
<value>要分片的封包類型:tlshello (TLS ClientHello) 或 1-1 到 1-5 (前 N 個 TCP 封包)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>分片長度</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLengthTip" xml:space="preserve">
|
||||
<value>分片大小範圍(位元組),如 50-100。第一個值必須 <= 第二個值。留空使用預設值。</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>分片間隔</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentIntervalTip" xml:space="preserve">
|
||||
<value>分片間延遲(毫秒),如 10-20。範圍 1-100。第一個值必須 <= 第二個值。留空使用預設值。</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplit" xml:space="preserve">
|
||||
<value>最大分片數</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentMaxSplitTip" xml:space="preserve">
|
||||
<value>每個封包最大分片數 (0-10000, 0=無限制)。僅 Xray。留空使用預設值。</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelay" xml:space="preserve">
|
||||
<value>回退延遲</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentFallbackDelayTip" xml:space="preserve">
|
||||
<value>ACK 偵測不可用時的回退延遲 (如 500ms)。僅 sing-box。留空使用預設值。</value>
|
||||
</data>
|
||||
<data name="FillFragmentParameterError" xml:space="preserve">
|
||||
<value>無效的範圍格式。請使用 'from-to' (如 50-100)。</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>啟用 sing-box(規則集檔案)的快取檔案</value>
|
||||
</data>
|
||||
@@ -1707,9 +1740,6 @@
|
||||
<data name="TbSettingsSendThroughTip" xml:space="preserve">
|
||||
<value>適用於多網路介面環境,請填寫本機 IPv4 位址</value>
|
||||
</data>
|
||||
<data name="FillCorrectSendThroughIPv4" xml:space="preserve">
|
||||
<value>請填寫正確的 SendThrough IPv4 位址。</value>
|
||||
</data>
|
||||
<data name="TbSettingsBindInterface" xml:space="preserve">
|
||||
<value>綁定網路介面</value>
|
||||
</data>
|
||||
@@ -1761,19 +1791,7 @@
|
||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||
<value>將封包末端拆分為更小的片段進行傳送,可能會影響吞吐量與延遲</value>
|
||||
</data>
|
||||
<data name="TbEnableNetBridge" xml:space="preserve">
|
||||
<value>啟用進程流量劫持(實驗性),類似 Proxifer 功能;可以和系統代理並存;和 TUN 模式衝突,請不要同時開啟;請在防火牆中允許 v2rayN 使用專用網絡</value>
|
||||
</data>
|
||||
<data name="NetBridgeRuleTips" xml:space="preserve">
|
||||
<value>需要代理程式的進程名,多個進程請用逗號分隔;不在清單中的進程也會被劫持,但是會直連</value>
|
||||
</data>
|
||||
<data name="menuNetBridge" xml:space="preserve">
|
||||
<value>進程流量劫持(實驗性)</value>
|
||||
</data>
|
||||
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||
<value>DNS 透過 Bridge</value>
|
||||
</data>
|
||||
<data name="TbSaveNetBridgeRule" xml:space="preserve">
|
||||
<value>保存規則</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -90,7 +90,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NetBridge\src\NetBridgeLib\NetBridgeLib.csproj" />
|
||||
<ProjectReference Include="..\ServiceLib.UdpTest\ServiceLib.UdpTest.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -66,10 +66,6 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!(context.IsTunEnabled || context.IsWindows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var outbound in _coreConfig.outbounds ?? [])
|
||||
{
|
||||
outbound.bind_interface = ShouldBindNet(outbound) ? bindInterface : null;
|
||||
@@ -83,10 +79,6 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!Utils.IsLocalIP(sendThrough))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var outbound in _coreConfig.outbounds ?? [])
|
||||
{
|
||||
|
||||
@@ -217,11 +217,17 @@ public partial class CoreConfigSingboxService
|
||||
|
||||
if (!protocolExtra.SalamanderPass.IsNullOrEmpty())
|
||||
{
|
||||
var isGecko = !protocolExtra.GeckoMinPacketSize.IsNullOrEmpty() || !protocolExtra.GeckoMaxPacketSize.IsNullOrEmpty();
|
||||
outbound.obfs = new()
|
||||
{
|
||||
type = "salamander",
|
||||
type = isGecko ? "gecko" : "salamander",
|
||||
password = protocolExtra.SalamanderPass.TrimEx(),
|
||||
};
|
||||
if (isGecko)
|
||||
{
|
||||
outbound.obfs.min_packet_size = protocolExtra.GeckoMinPacketSize.ToInt();
|
||||
outbound.obfs.max_packet_size = protocolExtra.GeckoMaxPacketSize.ToInt();
|
||||
}
|
||||
}
|
||||
int? upMbps = protocolExtra?.UpMbps is { } su and >= 0
|
||||
? su
|
||||
@@ -264,6 +270,22 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
}
|
||||
|
||||
if (HyRealm.TryParse(protocolExtra.Hy2RealmUrl, out var realm)
|
||||
&& realm is not null)
|
||||
{
|
||||
var realm4Sbox = new HyRealm4Sbox()
|
||||
{
|
||||
server_url = realm.ToServerUrl(),
|
||||
token = realm.Token,
|
||||
realm_id = realm.RealmName,
|
||||
stun_servers = realm.StunList?.Count > 0 ? realm.StunList : null,
|
||||
};
|
||||
outbound.realm = realm4Sbox;
|
||||
outbound.server = null;
|
||||
outbound.server_port = null;
|
||||
outbound.server_ports = null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EConfigType.TUIC:
|
||||
|
||||
@@ -89,22 +89,25 @@ public partial class CoreConfigV2rayService
|
||||
EMultipleLoad.RoundRobin => "roundRobin",
|
||||
EMultipleLoad.LeastPing => "leastPing",
|
||||
EMultipleLoad.LeastLoad => "leastLoad",
|
||||
EMultipleLoad.Fallback => "leastLoad",
|
||||
_ => "roundRobin",
|
||||
};
|
||||
var balancerTag = $"{selector}{Global.BalancerTagSuffix}";
|
||||
var balancer = new BalancersItem4Ray
|
||||
{
|
||||
selector = [selector],
|
||||
strategy = new()
|
||||
strategy = strategyType == "leastLoad" ? new()
|
||||
{
|
||||
type = strategyType,
|
||||
settings = new()
|
||||
{
|
||||
expected = 1,
|
||||
tolerance = multipleLoad == EMultipleLoad.Fallback ? 0.2 : null,
|
||||
maxRTT = multipleLoad == EMultipleLoad.Fallback ? "5000ms" : null,
|
||||
},
|
||||
},
|
||||
} : null,
|
||||
tag = balancerTag,
|
||||
fallbackTag = multipleLoad == EMultipleLoad.Fallback ? Global.DirectTag : null,
|
||||
fallbackTag = _coreConfig.outbounds?.FirstOrDefault(o => o.tag.StartsWith(selector))?.tag,
|
||||
};
|
||||
_coreConfig.routing.balancers ??= [];
|
||||
_coreConfig.routing.balancers.Add(balancer);
|
||||
|
||||
@@ -138,10 +138,6 @@ public partial class CoreConfigV2rayService
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!(context.IsTunEnabled || context.IsWindows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var outbound in _coreConfig.outbounds ?? [])
|
||||
{
|
||||
if (!ShouldBindNet(outbound))
|
||||
@@ -176,10 +172,6 @@ public partial class CoreConfigV2rayService
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!Utils.IsLocalIP(sendThrough))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var outbound in _coreConfig.outbounds ?? [])
|
||||
{
|
||||
|
||||
@@ -607,16 +607,32 @@ public partial class CoreConfigV2rayService
|
||||
quicParams.congestion = "bbr";
|
||||
}
|
||||
hy2Finalmask.quicParams = quicParams;
|
||||
hy2Finalmask.udp ??= [];
|
||||
if (HyRealm.TryParse(protocolExtra.Hy2RealmUrl, out var realm)
|
||||
&& realm is not null)
|
||||
{
|
||||
hy2Finalmask.udp.Add(new Mask4Ray
|
||||
{
|
||||
type = "realm",
|
||||
settings = new MaskSettings4Ray { url = realm.ToUriForFinalmask(), stunServers = realm.StunList },
|
||||
});
|
||||
}
|
||||
if (!protocolExtra.SalamanderPass.IsNullOrEmpty())
|
||||
{
|
||||
hy2Finalmask.udp =
|
||||
[
|
||||
new Mask4Ray
|
||||
{
|
||||
type = "salamander",
|
||||
settings = new MaskSettings4Ray { password = protocolExtra.SalamanderPass.TrimEx(), }
|
||||
}
|
||||
];
|
||||
var isGecko = !protocolExtra.GeckoMinPacketSize.IsNullOrEmpty() || !protocolExtra.GeckoMaxPacketSize.IsNullOrEmpty();
|
||||
var salamanderSettings = new MaskSettings4Ray
|
||||
{
|
||||
password = protocolExtra.SalamanderPass.TrimEx(),
|
||||
};
|
||||
if (isGecko)
|
||||
{
|
||||
salamanderSettings.packetSize = $"{protocolExtra.GeckoMinPacketSize}-{protocolExtra.GeckoMaxPacketSize}";
|
||||
}
|
||||
hy2Finalmask.udp.Add(new Mask4Ray
|
||||
{
|
||||
type = "salamander",
|
||||
settings = salamanderSettings,
|
||||
});
|
||||
}
|
||||
streamSettings.hysteriaSettings = new()
|
||||
{
|
||||
@@ -896,9 +912,17 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
private (Mask4Ray tcpFragment, Mask4Ray udpNoise) BuildFragmentsMasks()
|
||||
{
|
||||
var configPackets = _config.Fragment4RayItem?.Packets ?? "tlshello";
|
||||
var configLength = _config.Fragment4RayItem?.Length ?? "50-100";
|
||||
var configDelay = _config.Fragment4RayItem?.Interval ?? "10-20";
|
||||
var configPackets = _config.Fragment4RayItem?.Packets.NullIfEmpty() ?? "tlshello";
|
||||
var configLength = _config.Fragment4RayItem?.Length.NullIfEmpty() ?? "50-100";
|
||||
var configDelay = _config.Fragment4RayItem?.Interval.NullIfEmpty() ?? "10-20";
|
||||
var configMaxSplit = _config.Fragment4RayItem?.MaxSplit.NullIfEmpty() ?? "0";
|
||||
|
||||
var maxSplit = 0;
|
||||
var parts = configMaxSplit.Split('-');
|
||||
if (parts.Length > 0 && int.TryParse(parts[0], out var ms))
|
||||
{
|
||||
maxSplit = ms;
|
||||
}
|
||||
|
||||
var fragmentMask = new Mask4Ray
|
||||
{
|
||||
@@ -908,6 +932,7 @@ public partial class CoreConfigV2rayService
|
||||
packets = configPackets,
|
||||
length = configLength,
|
||||
delay = configDelay,
|
||||
maxSplit = maxSplit,
|
||||
}
|
||||
};
|
||||
var noiseMask = new Mask4Ray
|
||||
|
||||
@@ -46,7 +46,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
switch (actionType)
|
||||
{
|
||||
case ESpeedActionType.Tcping:
|
||||
await RunTcpingAsync(lstSelected);
|
||||
await RunTcpingAsync(lstSelected, exitLoopKey);
|
||||
break;
|
||||
|
||||
case ESpeedActionType.Realping:
|
||||
@@ -135,16 +135,28 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
return lstSelected;
|
||||
}
|
||||
|
||||
private async Task RunTcpingAsync(List<ServerTestItem> selecteds)
|
||||
private async Task RunTcpingAsync(List<ServerTestItem> selecteds, string exitLoopKey)
|
||||
{
|
||||
var pageSize = Math.Min(selecteds.Count, _speedTestPageSize);
|
||||
var lstBatch = GetTestBatchItem(selecteds, pageSize);
|
||||
|
||||
foreach (var lst in lstBatch)
|
||||
{
|
||||
if (ShouldStopTest(exitLoopKey))
|
||||
{
|
||||
await UpdateFunc("", ResUI.SpeedtestingSkip);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Task> tasks = [];
|
||||
|
||||
foreach (var it in lst)
|
||||
{
|
||||
if (ShouldStopTest(exitLoopKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
@@ -160,7 +172,14 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
if (ShouldStopTest(exitLoopKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.Delay(_delayInterval);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,15 @@ public class AddServerViewModel : MyReactiveObject
|
||||
[Reactive]
|
||||
public bool NaiveQuic { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Hy2RealmUrl { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int GeckoMinPacketSize { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int GeckoMaxPacketSize { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string RawHeaderType { get; set; }
|
||||
|
||||
@@ -302,6 +311,9 @@ public class AddServerViewModel : MyReactiveObject
|
||||
CongestionControl = protocolExtra.CongestionControl ?? string.Empty;
|
||||
InsecureConcurrency = protocolExtra.InsecureConcurrency > 0 ? protocolExtra.InsecureConcurrency : null;
|
||||
NaiveQuic = protocolExtra.NaiveQuic ?? false;
|
||||
Hy2RealmUrl = protocolExtra.Hy2RealmUrl ?? string.Empty;
|
||||
GeckoMinPacketSize = protocolExtra.GeckoMinPacketSize.ToInt();
|
||||
GeckoMaxPacketSize = protocolExtra.GeckoMaxPacketSize.ToInt();
|
||||
|
||||
RawHeaderType = transport.RawHeaderType ?? Global.None;
|
||||
Host = transport.Host ?? string.Empty;
|
||||
@@ -357,6 +369,16 @@ public class AddServerViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
}
|
||||
HyRealm? realm = null;
|
||||
if (!Hy2RealmUrl.IsNullOrEmpty())
|
||||
{
|
||||
var realmResult = HyRealm.TryParse(Hy2RealmUrl, out realm);
|
||||
if (!realmResult)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.InvalidHy2RealmUrl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : Enum.Parse<ECoreType>(CoreType);
|
||||
SelectedSource.AllowInsecure = AllowInsecure ? Global.StringTrue : Global.StringFalse;
|
||||
SelectedSource.MuxEnabled = MuxEnabled;
|
||||
@@ -403,6 +425,9 @@ public class AddServerViewModel : MyReactiveObject
|
||||
CongestionControl = CongestionControl.NullIfEmpty(),
|
||||
InsecureConcurrency = InsecureConcurrency > 0 ? InsecureConcurrency : null,
|
||||
NaiveQuic = NaiveQuic ? true : null,
|
||||
Hy2RealmUrl = realm?.ToUri().NullIfEmpty(),
|
||||
GeckoMinPacketSize = GeckoMinPacketSize > 0 ? GeckoMinPacketSize.ToString() : null,
|
||||
GeckoMaxPacketSize = GeckoMaxPacketSize > 0 ? GeckoMaxPacketSize.ToString() : null,
|
||||
});
|
||||
SelectedSource.SetTransportExtra(transport);
|
||||
|
||||
|
||||
@@ -43,14 +43,14 @@ public class CheckUpdateViewModel : MyReactiveObject
|
||||
|
||||
private void RefreshCheckUpdateItems()
|
||||
{
|
||||
var models = CoreInfoManager.Instance.GetCheckUpdateCoreTypes()
|
||||
.Select(t => GetCheckUpdateModel(t))
|
||||
.ToList();
|
||||
|
||||
models.Add(GetGeoFileCheckUpdateModel());
|
||||
|
||||
CheckUpdateModels.Clear();
|
||||
|
||||
foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes())
|
||||
{
|
||||
CheckUpdateModels.Add(GetCheckUpdateModel(type));
|
||||
}
|
||||
|
||||
CheckUpdateModels.Add(GetGeoFileCheckUpdateModel());
|
||||
CheckUpdateModels.AddRange(models);
|
||||
}
|
||||
|
||||
private CheckUpdateModel GetCheckUpdateModel(ECoreType coreType)
|
||||
|
||||
@@ -187,6 +187,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
var selectedName = SelectedGroup?.Name;
|
||||
ProxyGroups.Clear();
|
||||
|
||||
var lstProxyGroups = new List<ClashProxyModel>();
|
||||
var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups();
|
||||
if (proxyGroups is { Count: > 0 })
|
||||
{
|
||||
@@ -200,7 +201,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ProxyGroups.Add(new ClashProxyModel()
|
||||
lstProxyGroups.Add(new ClashProxyModel()
|
||||
{
|
||||
Now = item.now,
|
||||
Name = item.name,
|
||||
@@ -216,12 +217,12 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = ProxyGroups.FirstOrDefault(t => t.Name == kv.Key);
|
||||
var item = lstProxyGroups.FirstOrDefault(t => t.Name == kv.Key);
|
||||
if (item != null && item.Name.IsNotEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ProxyGroups.Add(new ClashProxyModel()
|
||||
lstProxyGroups.Add(new ClashProxyModel()
|
||||
{
|
||||
Now = kv.Value.now,
|
||||
Name = kv.Key,
|
||||
@@ -229,6 +230,8 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
});
|
||||
}
|
||||
|
||||
ProxyGroups.AddRange(lstProxyGroups);
|
||||
|
||||
if (ProxyGroups is { Count: > 0 })
|
||||
{
|
||||
if (selectedName != null && ProxyGroups.Any(t => t.Name == selectedName))
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class NetBridgeViewModel : MyReactiveObject
|
||||
{
|
||||
[Reactive]
|
||||
public bool EnableNetBridge { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool EnabletDnsViaProxy { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string RuleProcess { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveRulesCmd { get; }
|
||||
|
||||
public NetBridgeViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
SaveRulesCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SaveRulesAsync();
|
||||
});
|
||||
|
||||
this.WhenAnyValue(x => x.EnableNetBridge)
|
||||
.Skip(1)
|
||||
.Subscribe(async enabled =>
|
||||
{
|
||||
await ToggleNetBridgeAsync(enabled);
|
||||
});
|
||||
|
||||
this.WhenAnyValue(x => x.EnabletDnsViaProxy)
|
||||
.Skip(1)
|
||||
.Subscribe(async enabled =>
|
||||
{
|
||||
await ToggleDnsViaProxyAsync(enabled);
|
||||
});
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
|
||||
private async Task Init()
|
||||
{
|
||||
EnabletDnsViaProxy = _config.NetBridgeItem.EnableDnsViaProxy;
|
||||
|
||||
_config.NetBridgeItem ??= new()
|
||||
{
|
||||
RuleProcess = string.Empty
|
||||
};
|
||||
|
||||
EnableNetBridge = false;
|
||||
if (_config.NetBridgeItem.RuleProcess.IsNullOrEmpty())
|
||||
{
|
||||
_config.NetBridgeItem.RuleProcess = "Chrome.exe";
|
||||
}
|
||||
RuleProcess = _config.NetBridgeItem.RuleProcess;
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task ToggleNetBridgeAsync(bool enabled)
|
||||
{
|
||||
await NetBridgeManager.Instance.Init(UpdateViewHandler);
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
var succeed = await NetBridgeManager.Instance.Start();
|
||||
if (succeed)
|
||||
{
|
||||
await NetBridgeManager.Instance.UpdateProxyConfig(Global.Loopback, AppManager.Instance.GetLocalPort(EInboundProtocol.socks));
|
||||
await NetBridgeManager.Instance.UpdateRoutes(RuleProcess);
|
||||
await NetBridgeManager.Instance.SetDnsViaProxy(EnabletDnsViaProxy);
|
||||
}
|
||||
NoticeManager.Instance.Enqueue(succeed ? ResUI.OperationSuccess : ResUI.OperationFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
var succeed = await NetBridgeManager.Instance.Stop();
|
||||
NoticeManager.Instance.Enqueue(succeed ? ResUI.OperationSuccess : ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ToggleDnsViaProxyAsync(bool enabled)
|
||||
{
|
||||
_config.NetBridgeItem.EnableDnsViaProxy = enabled;
|
||||
|
||||
await NetBridgeManager.Instance.SetDnsViaProxy(enabled);
|
||||
}
|
||||
|
||||
/// </summary>
|
||||
private async Task SaveRulesAsync()
|
||||
{
|
||||
_config.NetBridgeItem ??= new();
|
||||
|
||||
var normalizedRuleProcess = RuleProcess;
|
||||
_config.NetBridgeItem.RuleProcess = normalizedRuleProcess;
|
||||
RuleProcess = normalizedRuleProcess;
|
||||
|
||||
if (await ConfigHandler.SaveConfig(_config) != 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
await NetBridgeManager.Instance.Init(UpdateViewHandler);
|
||||
if (EnableNetBridge)
|
||||
{
|
||||
var routesUpdated = await NetBridgeManager.Instance.UpdateRoutes(normalizedRuleProcess);
|
||||
NoticeManager.Instance.Enqueue(routesUpdated ? ResUI.OperationSuccess : ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(bool isError, string msg)
|
||||
{
|
||||
NoticeManager.Instance.SendMessageEx(msg);
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,10 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
[Reactive] public int? HyDownMbps { get; set; }
|
||||
[Reactive] public bool EnableFragment { get; set; }
|
||||
[Reactive] public bool EnableFinalFragment { get; set; }
|
||||
[Reactive] public string FragmentPackets { get; set; }
|
||||
[Reactive] public string FragmentLength { get; set; }
|
||||
[Reactive] public string FragmentInterval { get; set; }
|
||||
[Reactive] public string FragmentMaxSplit { get; set; }
|
||||
|
||||
#endregion Core
|
||||
|
||||
@@ -163,6 +167,10 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
HyDownMbps = _config.HysteriaItem.DownMbps;
|
||||
EnableFragment = _config.CoreBasicItem.EnableFragment;
|
||||
EnableFinalFragment = _config.CoreBasicItem.EnableFinalFragment;
|
||||
FragmentPackets = _config.Fragment4RayItem?.Packets;
|
||||
FragmentLength = _config.Fragment4RayItem?.Length;
|
||||
FragmentInterval = _config.Fragment4RayItem?.Interval;
|
||||
FragmentMaxSplit = _config.Fragment4RayItem?.MaxSplit;
|
||||
|
||||
#endregion Core
|
||||
|
||||
@@ -300,12 +308,6 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
|
||||
return;
|
||||
}
|
||||
var sendThroughValue = SendThrough.TrimEx();
|
||||
if (sendThroughValue.IsNotEmpty() && !Utils.IsIpv4(sendThroughValue))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillCorrectSendThroughIPv4);
|
||||
return;
|
||||
}
|
||||
var needReboot = EnableStatistics != _config.GuiItem.EnableStatistics
|
||||
|| DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed
|
||||
|| EnableDragDropSort != _config.UiItem.EnableDragDropSort
|
||||
@@ -349,8 +351,31 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
_config.CoreBasicItem.EnableCacheFile4Sbox = EnableCacheFile4Sbox;
|
||||
_config.HysteriaItem.UpMbps = HyUpMbps ?? 0;
|
||||
_config.HysteriaItem.DownMbps = HyDownMbps ?? 0;
|
||||
if (EnableFragment)
|
||||
{
|
||||
if (!Utils.TryParseRange(FragmentLength, 0, int.MaxValue, out _, out _))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
|
||||
return;
|
||||
}
|
||||
if (!Utils.TryParseRange(FragmentInterval, 1, 100, out _, out _))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
|
||||
return;
|
||||
}
|
||||
if (FragmentMaxSplit.IsNotEmpty()
|
||||
&& !Utils.TryParseMaxSplit(FragmentMaxSplit, 0, 10000, out _, out _))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_config.CoreBasicItem.EnableFragment = EnableFragment;
|
||||
_config.CoreBasicItem.EnableFinalFragment = EnableFinalFragment;
|
||||
_config.Fragment4RayItem.Packets = FragmentPackets;
|
||||
_config.Fragment4RayItem.Length = FragmentLength;
|
||||
_config.Fragment4RayItem.Interval = FragmentInterval;
|
||||
_config.Fragment4RayItem.MaxSplit = FragmentMaxSplit;
|
||||
|
||||
_config.GuiItem.AutoRun = AutoRun;
|
||||
_config.GuiItem.EnableStatistics = EnableStatistics;
|
||||
|
||||
@@ -161,19 +161,17 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
||||
}
|
||||
|
||||
public async Task RefreshSubscriptions()
|
||||
private async Task RefreshSubscriptions()
|
||||
{
|
||||
var subItems = await AppManager.Instance.SubItems();
|
||||
subItems.Insert(0, new SubItem { Remarks = ResUI.AllGroupServers });
|
||||
|
||||
SubItems.Clear();
|
||||
SubItems.AddRange(subItems);
|
||||
|
||||
SubItems.Add(new SubItem { Remarks = ResUI.AllGroupServers });
|
||||
|
||||
foreach (var item in await AppManager.Instance.SubItems())
|
||||
{
|
||||
SubItems.Add(item);
|
||||
}
|
||||
SelectedSub = (_config.SubIndexId.IsNotEmpty()
|
||||
? SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId)
|
||||
: null) ?? SubItems.FirstOrDefault();
|
||||
? subItems.FirstOrDefault(t => t.Id == _config.SubIndexId)
|
||||
: null) ?? subItems.FirstOrDefault();
|
||||
}
|
||||
|
||||
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||
|
||||
@@ -397,17 +397,15 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
private async Task RefreshSubscriptions()
|
||||
{
|
||||
var subItems = await AppManager.Instance.SubItems();
|
||||
subItems.Insert(0, new SubItem { Remarks = ResUI.AllGroupServers });
|
||||
|
||||
SubItems.Clear();
|
||||
SubItems.AddRange(subItems);
|
||||
|
||||
SubItems.Add(new SubItem { Remarks = ResUI.AllGroupServers });
|
||||
|
||||
foreach (var item in await AppManager.Instance.SubItems())
|
||||
{
|
||||
SubItems.Add(item);
|
||||
}
|
||||
SelectedSub = (_config.SubIndexId.IsNotEmpty()
|
||||
? SubItems.FirstOrDefault(t => t.Id == _config.SubIndexId)
|
||||
: null) ?? SubItems.FirstOrDefault();
|
||||
? subItems.FirstOrDefault(t => t.Id == _config.SubIndexId)
|
||||
: null) ?? subItems.FirstOrDefault();
|
||||
}
|
||||
|
||||
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||
|
||||
@@ -57,7 +57,7 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
|
||||
{
|
||||
Domain = Utils.Convert2Comma(Domain);
|
||||
IP = Utils.Convert2Comma(IP);
|
||||
Process = Utils.Convert2Comma(Process);
|
||||
Process = Utils.ParseProcess(Process);
|
||||
|
||||
if (AutoSort)
|
||||
{
|
||||
|
||||
@@ -95,6 +95,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
||||
{
|
||||
RulesItems.Clear();
|
||||
|
||||
var models = new List<RulesItemModel>();
|
||||
foreach (var item in _rules)
|
||||
{
|
||||
var it = new RulesItemModel()
|
||||
@@ -110,8 +111,9 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
||||
Enabled = item.Enabled,
|
||||
Remarks = item.Remarks,
|
||||
};
|
||||
RulesItems.Add(it);
|
||||
models.Add(it);
|
||||
}
|
||||
RulesItems.AddRange(models);
|
||||
}
|
||||
|
||||
public async Task RuleEditAsync(bool blNew)
|
||||
|
||||
@@ -83,6 +83,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
||||
public async Task RefreshRoutingItems()
|
||||
{
|
||||
RoutingItems.Clear();
|
||||
var models = new List<RoutingItemModel>();
|
||||
|
||||
var routings = await AppManager.Instance.RoutingItems();
|
||||
foreach (var item in routings)
|
||||
@@ -98,8 +99,9 @@ public class RoutingSettingViewModel : MyReactiveObject
|
||||
CustomRulesetPath4Singbox = item.CustomRulesetPath4Singbox,
|
||||
Sort = item.Sort,
|
||||
};
|
||||
RoutingItems.Add(it);
|
||||
models.Add(it);
|
||||
}
|
||||
RoutingItems.AddRange(models);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -234,6 +234,8 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
await RefreshRoutingsMenu();
|
||||
await InboundDisplayStatus();
|
||||
await ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, true);
|
||||
|
||||
BlRouting = true;
|
||||
}
|
||||
|
||||
public void InitUpdateView(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
@@ -312,18 +314,20 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
|
||||
var models = new List<ComboItem>();
|
||||
BlServers = true;
|
||||
foreach (var it in lstModel)
|
||||
{
|
||||
var name = it.GetSummary();
|
||||
|
||||
var item = new ComboItem() { ID = it.IndexId, Text = name };
|
||||
Servers.Add(item);
|
||||
models.Add(item);
|
||||
if (_config.IndexId == it.IndexId)
|
||||
{
|
||||
SelectedServer = item;
|
||||
}
|
||||
}
|
||||
Servers.AddRange(models);
|
||||
}
|
||||
|
||||
private void ServerSelectedChanged(bool c)
|
||||
@@ -408,18 +412,12 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
|
||||
private async Task RefreshRoutingsMenu()
|
||||
{
|
||||
RoutingItems.Clear();
|
||||
|
||||
BlRouting = true;
|
||||
var routings = await AppManager.Instance.RoutingItems();
|
||||
foreach (var item in routings)
|
||||
{
|
||||
RoutingItems.Add(item);
|
||||
if (item.IsActive)
|
||||
{
|
||||
SelectedRouting = item;
|
||||
}
|
||||
}
|
||||
|
||||
RoutingItems.Clear();
|
||||
RoutingItems.AddRange(routings);
|
||||
|
||||
SelectedRouting = routings.FirstOrDefault(t => t.IsActive == true);
|
||||
}
|
||||
|
||||
private async Task RoutingSelectedChangedAsync(bool c)
|
||||
|
||||
@@ -374,7 +374,7 @@
|
||||
Grid.Row="2"
|
||||
ColumnDefinitions="300,Auto,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
@@ -388,61 +388,110 @@
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
<Button
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource MarginLr4}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbPath7}" />
|
||||
<TextBox
|
||||
x:Name="txtPath7"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
Classes="IconButton">
|
||||
<Button.Content>
|
||||
<PathIcon Data="{StaticResource SemiIconMore}">
|
||||
<PathIcon.RenderTransform>
|
||||
<RotateTransform Angle="90" />
|
||||
</PathIcon.RenderTransform>
|
||||
</PathIcon>
|
||||
</Button.Content>
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbPath7}" />
|
||||
<TextBox
|
||||
x:Name="txtPath7"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
<TextBlock
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbGeckoPacketSize}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox
|
||||
x:Name="txtMinGeckoPacketSize"
|
||||
Width="90"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="Min" />
|
||||
<TextBox
|
||||
x:Name="txtMaxGeckoPacketSize"
|
||||
Width="90"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="Max" />
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbHy2RealmUrl}" />
|
||||
<TextBox
|
||||
x:Name="txtHy2RealmUrl"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
<TextBlock
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbHy2RealmUrlTip}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbPorts7}" />
|
||||
<TextBox
|
||||
x:Name="txtPorts7"
|
||||
Grid.Row="3"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="1000-2000,3000,4000" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbPorts7Tips}" />
|
||||
Text="{x:Static resx:ResUI.TbPorts7Tips}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbHopInt7}" />
|
||||
<TextBox
|
||||
x:Name="txtHopInt7"
|
||||
Grid.Row="4"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsHysteriaBandwidth}" />
|
||||
<StackPanel
|
||||
Grid.Row="5"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal">
|
||||
<TextBox
|
||||
|
||||
@@ -173,6 +173,9 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
this.Bind(ViewModel, vm => vm.HopInterval, v => v.txtHopInt7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.UpMbps, v => v.txtUpMbps7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.DownMbps, v => v.txtDownMbps7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Hy2RealmUrl, v => v.txtHy2RealmUrl.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.GeckoMinPacketSize, v => v.txtMinGeckoPacketSize.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.GeckoMaxPacketSize, v => v.txtMaxGeckoPacketSize.Text).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.TUIC:
|
||||
|
||||
@@ -89,7 +89,6 @@
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="menuBackupAndRestore" Header="{x:Static resx:ResUI.menuBackupAndRestore}" />
|
||||
<MenuItem x:Name="menuOpenTheFileLocation" Header="{x:Static resx:ResUI.menuOpenTheFileLocation}" />
|
||||
<MenuItem x:Name="menuNetBridge" Header="{x:Static resx:ResUI.menuNetBridge}" />
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem x:Name="menuHelp" Header="{x:Static resx:ResUI.menuHelp}">
|
||||
|
||||
@@ -12,7 +12,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
private readonly WindowNotificationManager? _manager;
|
||||
private CheckUpdateView? _checkUpdateView;
|
||||
private BackupAndRestoreView? _backupAndRestoreView;
|
||||
private NetBridgeView? _netBridgeView;
|
||||
private bool _blCloseByUser = false;
|
||||
|
||||
public MainWindow()
|
||||
@@ -29,7 +28,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
btnNewUpdate.Click += MenuCheckUpdate_Click;
|
||||
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
|
||||
menuClose.Click += MenuClose_Click;
|
||||
menuNetBridge.Click += MenuNetBridge_Click;
|
||||
|
||||
ViewModel = new MainWindowViewModel(UpdateViewHandler);
|
||||
|
||||
@@ -167,7 +165,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
{
|
||||
Title = $"{Utils.GetVersion()}";
|
||||
menuAddServerViaScan.IsVisible = false;
|
||||
menuNetBridge.IsVisible = false;
|
||||
}
|
||||
|
||||
if (_config.UiItem.AutoHideStartup && Utils.IsWindows())
|
||||
@@ -382,19 +379,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
DialogHost.Show(_backupAndRestoreView);
|
||||
}
|
||||
|
||||
private void MenuNetBridge_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Utils.IsAdministrator())
|
||||
{
|
||||
_netBridgeView ??= new NetBridgeView();
|
||||
DialogHost.Show(_netBridgeView);
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.SendMessageAndEnqueue(ResUI.RunAsAdmin);
|
||||
}
|
||||
}
|
||||
|
||||
private async void MenuClose_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (await UI.ShowYesNo(this, ResUI.menuExitTips) != ButtonResult.Yes)
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="v2rayN.Desktop.Views.NetBridgeView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
d:DesignHeight="400"
|
||||
d:DesignWidth="600"
|
||||
x:DataType="vms:NetBridgeViewModel"
|
||||
mc:Ignorable="d">
|
||||
<Border
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Theme="{DynamicResource CardBorder}">
|
||||
|
||||
<DockPanel Margin="{StaticResource Margin8}">
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<ToggleSwitch
|
||||
x:Name="togEnableNetBridge"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbEnableNetBridge}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<ToggleSwitch
|
||||
x:Name="togEnabletDnsViaProxy"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbEnabletDnsViaProxy}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<DockPanel>
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.NetBridgeRuleTips}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Right"
|
||||
DockPanel.Dock="Bottom"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
Content="{x:Static resx:ResUI.TbSaveNetBridgeRule}"
|
||||
IsDefault="True" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBox
|
||||
x:Name="txtRuleProcess"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Stretch"
|
||||
AcceptsReturn="True"
|
||||
BorderThickness="1"
|
||||
Classes="TextArea"
|
||||
TextWrapping="Wrap" />
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -1,25 +0,0 @@
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class NetBridgeView : ReactiveUserControl<NetBridgeViewModel>
|
||||
{
|
||||
public NetBridgeView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ViewModel = new NetBridgeViewModel(UpdateViewHandler);
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.BindCommand(ViewModel, vm => vm.SaveRulesCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.EnableNetBridge, v => v.togEnableNetBridge.IsChecked).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.RuleProcess, v => v.txtRuleProcess.Text).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
@@ -300,6 +300,115 @@
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<Button
|
||||
Grid.Row="20"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
HorizontalAlignment="Left"
|
||||
Classes="IconButton">
|
||||
<Button.Content>
|
||||
<PathIcon Data="{StaticResource SemiIconMore}">
|
||||
<PathIcon.RenderTransform>
|
||||
<RotateTransform Angle="90" />
|
||||
</PathIcon.RenderTransform>
|
||||
</PathIcon>
|
||||
</Button.Content>
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<StackPanel Margin="{StaticResource Margin8}">
|
||||
<Grid ColumnDefinitions="Auto,Auto,400" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentPackets}" />
|
||||
<ComboBox
|
||||
x:Name="cmbFragmentPackets"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Width="120"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentPacketsTip}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentLength}" />
|
||||
<TextBox
|
||||
x:Name="txtFragmentLength"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="120"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
Watermark="50-100" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentLengthTip}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentInterval}" />
|
||||
<TextBox
|
||||
x:Name="txtFragmentInterval"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="120"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
Watermark="10-20" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentIntervalTip}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentMaxSplit}" />
|
||||
<TextBox
|
||||
x:Name="txtFragmentMaxSplit"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Width="120"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
Watermark="0" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentMaxSplitTip}"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="21"
|
||||
Grid.Column="0"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Avalonia.Media;
|
||||
using v2rayN.Desktop.Base;
|
||||
using v2rayN.Desktop.Common;
|
||||
|
||||
@@ -36,6 +35,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
|
||||
cmbMtu.ItemsSource = Global.TunMtus;
|
||||
cmbStack.ItemsSource = Global.TunStacks;
|
||||
cmbIcmpRoutingPolicy.ItemsSource = Global.TunIcmpRoutingPolicies;
|
||||
cmbFragmentPackets.ItemsSource = Global.FragmentPacketsOptions;
|
||||
|
||||
cmbCoreType1.ItemsSource = Global.CoreTypes;
|
||||
cmbCoreType2.ItemsSource = Global.CoreTypes;
|
||||
@@ -84,6 +84,10 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
|
||||
this.Bind(ViewModel, vm => vm.HyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.EnableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.EnableFinalFragment, v => v.togenableFinalFragment.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.FragmentPackets, v => v.cmbFragmentPackets.SelectedItem).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.FragmentLength, v => v.txtFragmentLength.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.FragmentInterval, v => v.txtFragmentInterval.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.FragmentMaxSplit, v => v.txtFragmentMaxSplit.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.AutoRun, v => v.togAutoRun.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.EnableStatistics, v => v.togEnableStatistics.IsChecked).DisposeWith(disposables);
|
||||
|
||||
@@ -35,8 +35,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceLib.Tests", "Service
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceLib.UdpTest", "ServiceLib.UdpTest\ServiceLib.UdpTest.csproj", "{CE9D9298-0289-4718-2522-B236489F2912}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetBridgeLib", "NetBridge\src\NetBridgeLib\NetBridgeLib.csproj", "{4AF81950-DD4E-A636-6BD5-B4A7839D87B0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -71,10 +69,6 @@ Global
|
||||
{CE9D9298-0289-4718-2522-B236489F2912}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CE9D9298-0289-4718-2522-B236489F2912}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CE9D9298-0289-4718-2522-B236489F2912}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4AF81950-DD4E-A636-6BD5-B4A7839D87B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4AF81950-DD4E-A636-6BD5-B4A7839D87B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4AF81950-DD4E-A636-6BD5-B4A7839D87B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4AF81950-DD4E-A636-6BD5-B4A7839D87B0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
</Folder>
|
||||
<Project Path="AmazTool/AmazTool.csproj" />
|
||||
<Project Path="GlobalHotKeys/src/GlobalHotKeys/GlobalHotKeys.csproj" />
|
||||
<Project Path="NetBridge/src/NetBridgeLib/NetBridgeLib.csproj" />
|
||||
<Project Path="ServiceLib.Tests/ServiceLib.Tests.csproj" />
|
||||
<Project Path="ServiceLib.UdpTest/ServiceLib.UdpTest.csproj" Id="77930428-4dc4-4130-9031-6b9e714f5d20" />
|
||||
<Project Path="ServiceLib/ServiceLib.csproj" />
|
||||
|
||||
@@ -505,6 +505,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300" />
|
||||
@@ -526,21 +527,62 @@
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
<materialDesign:PopupBox
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbPath7}" />
|
||||
<TextBox
|
||||
x:Name="txtPath7"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
StaysOpen="True"
|
||||
Style="{StaticResource MaterialDesignToolForegroundPopupBox}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbPath7}" />
|
||||
<TextBox
|
||||
x:Name="txtPath7"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
<TextBlock
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbGeckoPacketSize}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox
|
||||
x:Name="txtMinGeckoPacketSize"
|
||||
Width="90"
|
||||
Margin="{StaticResource Margin4}"
|
||||
materialDesign:HintAssist.Hint="Min"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
<TextBox
|
||||
x:Name="txtMaxGeckoPacketSize"
|
||||
Width="90"
|
||||
Margin="{StaticResource Margin4}"
|
||||
materialDesign:HintAssist.Hint="Max"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbHy2RealmUrl}" />
|
||||
<TextBox
|
||||
x:Name="txtHy2RealmUrl"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
<TextBlock
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbHy2RealmUrlTip}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</materialDesign:PopupBox>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
@@ -561,10 +603,12 @@
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="2"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbPorts7Tips}" />
|
||||
Text="{x:Static resx:ResUI.TbPorts7Tips}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
|
||||
@@ -172,6 +172,9 @@ public partial class AddServerWindow
|
||||
this.Bind(ViewModel, vm => vm.HopInterval, v => v.txtHopInt7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.UpMbps, v => v.txtUpMbps7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.DownMbps, v => v.txtDownMbps7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Hy2RealmUrl, v => v.txtHy2RealmUrl.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.GeckoMinPacketSize, v => v.txtMinGeckoPacketSize.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.GeckoMaxPacketSize, v => v.txtMaxGeckoPacketSize.Text).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.TUIC:
|
||||
|
||||
@@ -233,10 +233,6 @@
|
||||
x:Name="menuOpenTheFileLocation"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuOpenTheFileLocation}" />
|
||||
<MenuItem
|
||||
x:Name="menuNetBridge"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuNetBridge}" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Separator />
|
||||
|
||||
@@ -10,7 +10,6 @@ public partial class MainWindow
|
||||
private static Config _config;
|
||||
private CheckUpdateView? _checkUpdateView;
|
||||
private BackupAndRestoreView? _backupAndRestoreView;
|
||||
private NetBridgeView? _netBridgeView;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
@@ -28,7 +27,6 @@ public partial class MainWindow
|
||||
menuCheckUpdate.Click += MenuCheckUpdate_Click;
|
||||
btnNewUpdate.Click += MenuCheckUpdate_Click;
|
||||
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
|
||||
menuNetBridge.Click += MenuNetBridge_Click;
|
||||
|
||||
ViewModel = new MainWindowViewModel(UpdateViewHandler);
|
||||
|
||||
@@ -378,19 +376,6 @@ public partial class MainWindow
|
||||
DialogHost.Show(_backupAndRestoreView, "RootDialog");
|
||||
}
|
||||
|
||||
private void MenuNetBridge_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Utils.IsAdministrator())
|
||||
{
|
||||
_netBridgeView ??= new NetBridgeView();
|
||||
DialogHost.Show(_netBridgeView, "RootDialog");
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.SendMessageAndEnqueue(ResUI.RunAsAdmin);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Event
|
||||
|
||||
#region UI
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
<reactiveui:ReactiveUserControl
|
||||
x:Class="v2rayN.Views.NetBridgeView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:v2rayN.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
Height="400"
|
||||
d:DesignHeight="400"
|
||||
d:DesignWidth="600"
|
||||
x:TypeArguments="vms:NetBridgeViewModel"
|
||||
Style="{StaticResource ViewGlobal}"
|
||||
mc:Ignorable="d">
|
||||
<DockPanel Margin="{StaticResource Margin8}">
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<ToggleButton
|
||||
x:Name="togEnableNetBridge"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbEnableNetBridge}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<ToggleButton
|
||||
x:Name="togEnabletDnsViaProxy"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbEnabletDnsViaProxy}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<DockPanel>
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.NetBridgeRuleTips}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Right"
|
||||
DockPanel.Dock="Bottom"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
Content="{x:Static resx:ResUI.TbSaveNetBridgeRule}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBox
|
||||
x:Name="txtRuleProcess"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Stretch"
|
||||
AcceptsReturn="True"
|
||||
BorderThickness="1"
|
||||
Style="{StaticResource DefTextBox}"
|
||||
TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto" />
|
||||
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</reactiveui:ReactiveUserControl>
|
||||
@@ -1,26 +0,0 @@
|
||||
namespace v2rayN.Views;
|
||||
|
||||
public partial class NetBridgeView
|
||||
{
|
||||
public NetBridgeView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ViewModel = new NetBridgeViewModel(UpdateViewHandler);
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.BindCommand(ViewModel, vm => vm.SaveRulesCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.EnableNetBridge, v => v.togEnableNetBridge.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.EnabletDnsViaProxy, v => v.togEnabletDnsViaProxy.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.RuleProcess, v => v.txtRuleProcess.Text).DisposeWith(disposables);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,13 @@
|
||||
Style="{StaticResource WindowGlobal}"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Popupbox.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<DockPanel Margin="{StaticResource Margin8}">
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin4}"
|
||||
@@ -366,6 +373,100 @@
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<materialDesign:PopupBox
|
||||
Grid.Row="20"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Left"
|
||||
StaysOpen="True"
|
||||
Style="{StaticResource MaterialDesignToolForegroundPopupBox}">
|
||||
<StackPanel Margin="{StaticResource Margin8}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="400" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentPackets}"
|
||||
TextWrapping="Wrap" />
|
||||
<ComboBox
|
||||
x:Name="cmbFragmentPackets"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentLength}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
x:Name="txtFragmentLength"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
materialDesign:HintAssist.Hint="50-100"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentInterval}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
x:Name="txtFragmentInterval"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
materialDesign:HintAssist.Hint="10-20"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentMaxSplit}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
x:Name="txtFragmentMaxSplit"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
materialDesign:HintAssist.Hint="0"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</materialDesign:PopupBox>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="21"
|
||||
Grid.Column="0"
|
||||
|
||||
@@ -32,6 +32,7 @@ public partial class OptionSettingWindow
|
||||
cmbMtu.ItemsSource = Global.TunMtus;
|
||||
cmbStack.ItemsSource = Global.TunStacks;
|
||||
cmbIcmpRoutingPolicy.ItemsSource = Global.TunIcmpRoutingPolicies;
|
||||
cmbFragmentPackets.ItemsSource = Global.FragmentPacketsOptions;
|
||||
|
||||
cmbCoreType1.ItemsSource = Global.CoreTypes;
|
||||
cmbCoreType2.ItemsSource = Global.CoreTypes;
|
||||
@@ -80,6 +81,10 @@ public partial class OptionSettingWindow
|
||||
this.Bind(ViewModel, vm => vm.HyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.EnableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.EnableFinalFragment, v => v.togenableFinalFragment.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.FragmentPackets, v => v.cmbFragmentPackets.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.FragmentLength, v => v.txtFragmentLength.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.FragmentInterval, v => v.txtFragmentInterval.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.FragmentMaxSplit, v => v.txtFragmentMaxSplit.Text).DisposeWith(disposables);
|
||||
|
||||
//this.Bind(ViewModel, vm => vm.Kcpmtu, v => v.txtKcpmtu.Text).DisposeWith(disposables);
|
||||
//this.Bind(ViewModel, vm => vm.Kcptti, v => v.txtKcptti.Text).DisposeWith(disposables);
|
||||
|
||||
Reference in New Issue
Block a user