Update On Fri Sep 20 20:34:48 CEST 2024

This commit is contained in:
github-action[bot]
2024-09-20 20:34:48 +02:00
parent 0bc2f74993
commit 94e24d57cc
91 changed files with 1699 additions and 917 deletions

1
.github/update.log vendored
View File

@@ -769,3 +769,4 @@ Update On Mon Sep 16 20:36:05 CEST 2024
Update On Tue Sep 17 20:34:06 CEST 2024
Update On Wed Sep 18 20:34:25 CEST 2024
Update On Thu Sep 19 20:35:43 CEST 2024
Update On Fri Sep 20 20:34:37 CEST 2024

View File

@@ -192,3 +192,23 @@ jobs:
gh release upload $TAG_NAME "$sha_file" --clobber
echo "Uploaded $sha_file to release $TAG_NAME"
done
- name: Upload AppImage to Github Artifact
if: ${{ inputs.arch == 'x86_64' }}
uses: actions/upload-artifact@v4
with:
name: Clash.Nyanpasu-linux-${{ inputs.arch }}-appimage
path: ./backend/target/**/*.AppImage
- name: Upload deb to Github Artifact
uses: actions/upload-artifact@v4
with:
name: Clash.Nyanpasu-linux-${{ inputs.arch }}-deb
path: |
./backend/target/**/*.deb
./backend/target/**/*.deb.sha256
- name: Upload rpm to Github Artifact
uses: actions/upload-artifact@v4
with:
name: Clash.Nyanpasu-linux-${{ inputs.arch }}-rpm
path: |
./backend/target/**/*.rpm
./backend/target/**/*.rpm.sha256

View File

@@ -132,3 +132,10 @@ jobs:
run: |
${{ inputs.nightly == true && 'pnpm build:nightly --target aarch64-apple-darwin' || 'pnpm build --target aarch64-apple-darwin' }}
pnpm upload:osx-aarch64
- name: Upload to Github Artifact
uses: actions/upload-artifact@v4
with:
name: Clash.Nyanpasu-macOS-${{ inputs.aarch64 == true && 'aarch64' || 'amd64' }}
path: |
./backend/target/**/*.dmg

View File

@@ -9,6 +9,12 @@ on:
type: boolean
default: false
fixed-webview:
description: "Fixed WebView"
required: true
type: boolean
default: false
nightly:
description: "Nightly prepare"
required: true
@@ -28,7 +34,7 @@ on:
options:
- x86_64
- i686
# - aarch64
- aarch64
workflow_call:
inputs:
@@ -38,6 +44,12 @@ on:
type: boolean
default: false
fixed-webview:
description: "Fixed WebView"
required: true
type: boolean
default: false
nightly:
description: "Nightly prepare"
required: true
@@ -103,53 +115,115 @@ jobs:
'i686' {
pnpm check --arch ia32 --sidecar-host i686-pc-windows-msvc
}
'aarch64' {
pnpm check --arch arm64 --sidecar-host aarch64-pc-windows-msvc
}
}
- name: Download fixed WebView
if: ${{ inputs.fixed-webview == true }}
run: |
$condition = '${{ inputs.arch }}'
switch ($condition) {
'x86_64' {
$arch= 'x64'
}
'i686' {
$arch = 'x86'
}
'aarch64' {
$arch = 'arm64'
}
}
$version = '127.0.2651.105'
$uri = "https://github.com/westinyang/WebView2RuntimeArchive/releases/download/$version/Microsoft.WebView2.FixedVersionRuntime.$version.$arch.cab"
$outfile = "Microsoft.WebView2.FixedVersionRuntime.$version.$arch.cab"
echo "Downloading $uri to $outfile"
invoke-webrequest -uri $uri -outfile $outfile
echo "Download finished, attempting to extract"
expand.exe $outfile -F:* ./backend/tauri
echo "Extraction finished"
- name: Prepare (Windows NSIS and Portable)
if: ${{ inputs.fixed-webview == false }}
run: ${{ inputs.nightly == true && 'pnpm prepare:nightly --nsis' || 'pnpm prepare:release --nsis' }}
- name: Prepare (Windows NSIS and Portable) with fixed WebView
if: ${{ inputs.fixed-webview == true }}
run: ${{ inputs.nightly == true && 'pnpm prepare:nightly --nsis --fixed-webview' || 'pnpm prepare:release --nsis --fixed-webview' }}
- name: Build UI
run: |
pnpm -F ui build
# TODO: optimize strategy
- name: Tauri build x86_64
uses: tauri-apps/tauri-action@v0
if: ${{ inputs.arch == 'x86_64' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
NIGHTLY: ${{ inputs.nightly == true && 'true' || 'false' }}
with:
tagName: ${{ inputs.tag }}
releaseName: "Clash Nyanpasu Dev"
releaseBody: "More new features are now supported."
releaseDraft: false
prerelease: true
tauriScript: pnpm tauri
args: ${{ inputs.nightly == true && '-f nightly -c ./backend/tauri/tauri.nightly.conf.json' || '-f default-meta' }}
run: |
pnpm tauri build ${{ inputs.nightly == true && '-f nightly -c ./backend/tauri/tauri.nightly.conf.json' || '-f default-meta' }}
- name: Tauri build i686
uses: tauri-apps/tauri-action@v0
if: ${{ inputs.arch != 'x86_64' }}
if: ${{ inputs.arch == 'i686' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
NIGHTLY: ${{ inputs.nightly == true && 'true' || 'false' }}
with:
tagName: ${{ inputs.tag }}
releaseName: "Clash Nyanpasu Dev"
releaseBody: "More new features are now supported."
releaseDraft: false
prerelease: true
tauriScript: pnpm tauri
args: ${{ inputs.nightly == true && '-f nightly -c ./backend/tauri/tauri.nightly.conf.json --target i686-pc-windows-msvc' || '-f default-meta --target i686-pc-windows-msvc' }}
run: |
pnpm tauri build ${{ inputs.nightly == true && '-f nightly -c ./backend/tauri/tauri.nightly.conf.json --target i686-pc-windows-msvc' || '-f default-meta --target i686-pc-windows-msvc' }}
- name: Tauri build arm64
if: ${{ inputs.arch == 'aarch64' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
NIGHTLY: ${{ inputs.nightly == true && 'true' || 'false' }}
run: |
pnpm tauri build ${{ inputs.nightly == true && '-f nightly -c ./backend/tauri/tauri.nightly.conf.json --target aarch64-pc-windows-msvc' || '-f default-meta --target aarch64-pc-windows-msvc' }}
- name: Rename fixed webview bundle name
if: ${{ inputs.fixed-webview == true }}
run: |
$files = Get-ChildItem -Path "./backend/target" -Recurse -Include "*.exe", "*.zip", "*.zip.sig" | Where-Object { $_.FullName -like "*\bundle\*" }
$condition = '${{ inputs.arch }}'
switch ($condition) {
'x86_64' {
$arch= 'x64'
}
'i686' {
$arch = 'x86'
}
'aarch64' {
$arch = 'arm64'
}
}
foreach ($file in $files) {
echo "Renaming $file"
$newname = $file.FullName -replace $arch, "fixed-webview-$arch"
Rename-Item -Path $file -NewName $newname
}
- name: Upload to release
run: |
$files = Get-ChildItem -Path "./backend/target" -Recurse -Include "*.exe", "*.zip", "*.zip.sig" | Where-Object { $_.FullName -like "*\bundle\*" }
foreach ($file in $files) {
echo "Uploading $file"
gh release upload ${{ inputs.tag }} "$file" --clobber
}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Portable Bundle
if: ${{ inputs.portable == true }}
run: |
pnpm portable
pnpm portable ${{ inputs.fixed-webview == true && '--fixed-webview' || '' }}
env:
RUST_ARCH: ${{ inputs.arch }}
TAG_NAME: ${{ inputs.tag }}
@@ -158,3 +232,18 @@ jobs:
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
NIGHTLY: ${{ inputs.nightly == true && 'true' || 'false' }}
VITE_WIN_PORTABLE: 1
- name: Upload NSIS Installer
uses: actions/upload-artifact@v4
with:
name: Clash.Nyanpasu-windows-${{ inputs.arch }}${{ inputs.fixed-webview == true && '-fixed-webview' || '' }}-nsis-installer
path: |
./backend/target/**/bundle/*.exe
- name: Upload portable
if: ${{ inputs.portable == true }}
uses: actions/upload-artifact@v4
with:
name: Clash.Nyanpasu-windows-${{ inputs.arch }}${{ inputs.fixed-webview == true && '-fixed-webview' || '' }}-portable
path: |
./*_portable.zip

View File

@@ -54,6 +54,11 @@ jobs:
run: pnpm updater:nightly
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update Nightly Fixed Webview Updater
if: ${{ inputs.nightly == true }}
run: pnpm updater:nightly --fixed-webview
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update Stable Updater
if: ${{ inputs.nightly == false }}
@@ -61,3 +66,10 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_BODY: ${{ inputs.release_body || github.event.release.body }}
- name: Update Stable Fixed Webview Updater
if: ${{ inputs.nightly == false }}
run: pnpm updater --fixed-webview
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_BODY: ${{ inputs.release_body || github.event.release.body }}

View File

@@ -51,3 +51,4 @@ jobs:
TELEGRAM_API_HASH: ${{ secrets.TELEGRAM_API_HASH }}
TELEGRAM_TO: "@keikolog"
TELEGRAM_TO_NIGHTLY: "@ClashNyanpasu"
WORKFLOW_RUN_ID: ${{ github.run_id }}

View File

@@ -24,10 +24,23 @@ jobs:
with:
portable: true
nightly: true
fixed-webview: false
arch: "x86_64"
tag: "pre-release"
secrets: inherit
windows_aarch64_build:
name: Windows aarch64 Build
uses: ./.github/workflows/deps-build-windows-nsis.yaml
needs: [delete_current_releases]
with:
portable: true
nightly: true
fixed-webview: false
arch: "aarch64"
tag: "pre-release"
secrets: inherit
windows_i686_build:
name: Windows i686 Build
uses: ./.github/workflows/deps-build-windows-nsis.yaml
@@ -35,10 +48,47 @@ jobs:
with:
portable: true
nightly: true
fixed-webview: false
arch: "i686"
tag: "pre-release"
secrets: inherit
windows_amd64_build_fixed_webview:
name: Windows x86_64 Build with Fixed WebView
uses: ./.github/workflows/deps-build-windows-nsis.yaml
needs: [delete_current_releases]
with:
portable: true
nightly: true
arch: "x86_64"
fixed-webview: true
tag: "pre-release"
secrets: inherit
windows_aarch64_build_fixed_webview:
name: Windows aarch64 Build with Fixed WebView
uses: ./.github/workflows/deps-build-windows-nsis.yaml
needs: [delete_current_releases]
with:
portable: true
nightly: true
arch: "aarch64"
fixed-webview: true
tag: "pre-release"
secrets: inherit
windows_i686_build_fixed_webview:
name: Windows i686 Build with Fixed WebView
uses: ./.github/workflows/deps-build-windows-nsis.yaml
needs: [delete_current_releases]
with:
portable: true
nightly: true
arch: "i686"
fixed-webview: true
tag: "pre-release"
secrets: inherit
linux_amd64_build:
name: Linux amd64 Build
uses: ./.github/workflows/deps-build-linux.yaml
@@ -79,15 +129,15 @@ jobs:
arch: "armhf"
secrets: inherit
linux_armel_build:
name: Linux armel Build
uses: ./.github/workflows/deps-build-linux.yaml
needs: [delete_current_releases]
with:
nightly: true
tag: "pre-release"
arch: "armel"
secrets: inherit
# linux_armel_build:
# name: Linux armel Build
# uses: ./.github/workflows/deps-build-linux.yaml
# needs: [delete_current_releases]
# with:
# nightly: true
# tag: "pre-release"
# arch: "armel"
# secrets: inherit
macos_amd64_build:
name: macOS amd64 Build
@@ -111,15 +161,18 @@ jobs:
update_tag:
name: Update tag
needs:
[
needs: [
windows_amd64_build,
windows_i686_build,
windows_aarch64_build,
windows_amd64_build_fixed_webview,
windows_i686_build_fixed_webview,
windows_aarch64_build_fixed_webview,
linux_amd64_build,
linux_i686_build,
linux_aarch64_build,
linux_armhf_build,
linux_armel_build,
# linux_armel_build,
macos_amd64_build,
macos_aarch64_build,
]

View File

@@ -228,23 +228,6 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "ashpd"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd884d7c72877a94102c3715f3b1cd09ff4fac28221add3e57cfbe25c236d093"
dependencies = [
"enumflags2",
"futures-channel",
"futures-util",
"rand 0.8.5",
"serde",
"serde_repr",
"tokio",
"url",
"zbus",
]
[[package]]
name = "ashpd"
version = "0.9.1"
@@ -1303,7 +1286,6 @@ dependencies = [
"bytes",
"chrono",
"clap",
"cocoa",
"colored",
"convert_case 0.6.0",
"ctrlc",
@@ -1333,7 +1315,9 @@ dependencies = [
"num_cpus",
"nyanpasu-ipc",
"nyanpasu-utils",
"objc",
"objc2",
"objc2-app-kit",
"objc2-foundation",
"once_cell",
"open",
"openssl",
@@ -1352,7 +1336,7 @@ dependencies = [
"regex",
"relative-path",
"reqwest",
"rfd 0.14.1",
"rfd",
"rs-snowflake",
"runas",
"rust-i18n",
@@ -2072,7 +2056,7 @@ dependencies = [
[[package]]
name = "dirs-utils"
version = "0.1.0"
source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#87ef4dd8b0d9cdbcb0ae7ee5d925dd5c277b729e"
source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#a17b1cf262de64adb4fa30dfda629762909ee766"
dependencies = [
"dirs-next",
"thiserror",
@@ -4725,7 +4709,7 @@ dependencies = [
[[package]]
name = "nyanpasu-ipc"
version = "1.0.6"
source = "git+https://github.com/LibNyanpasu/nyanpasu-service.git#06b52b490e1ef9fce7f798d45dff4c01faa54d5b"
source = "git+https://github.com/LibNyanpasu/nyanpasu-service.git#a9d91584893489ffb96741bfee3f5b20a4f2eba7"
dependencies = [
"anyhow",
"axum",
@@ -4750,7 +4734,7 @@ dependencies = [
[[package]]
name = "nyanpasu-utils"
version = "0.1.0"
source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#87ef4dd8b0d9cdbcb0ae7ee5d925dd5c277b729e"
source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#a17b1cf262de64adb4fa30dfda629762909ee766"
dependencies = [
"constcat",
"derive_builder",
@@ -5023,7 +5007,7 @@ dependencies = [
[[package]]
name = "os-utils"
version = "0.1.0"
source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#87ef4dd8b0d9cdbcb0ae7ee5d925dd5c277b729e"
source = "git+https://github.com/LibNyanpasu/nyanpasu-utils.git#a17b1cf262de64adb4fa30dfda629762909ee766"
dependencies = [
"nix 0.29.0",
"shared_child",
@@ -5282,9 +5266,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pest"
version = "2.7.12"
version = "2.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea"
checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9"
dependencies = [
"memchr",
"thiserror",
@@ -5293,9 +5277,9 @@ dependencies = [
[[package]]
name = "pest_derive"
version = "2.7.12"
version = "2.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d"
checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0"
dependencies = [
"pest",
"pest_generator",
@@ -5303,9 +5287,9 @@ dependencies = [
[[package]]
name = "pest_generator"
version = "2.7.12"
version = "2.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe"
checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e"
dependencies = [
"pest",
"pest_meta",
@@ -5316,9 +5300,9 @@ dependencies = [
[[package]]
name = "pest_meta"
version = "2.7.12"
version = "2.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174"
checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f"
dependencies = [
"once_cell",
"pest",
@@ -6151,37 +6135,13 @@ dependencies = [
"windows-registry",
]
[[package]]
name = "rfd"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a73a7337fc24366edfca76ec521f51877b114e42dab584008209cca6719251"
dependencies = [
"ashpd 0.8.1",
"block",
"dispatch",
"glib-sys",
"gobject-sys",
"gtk-sys",
"js-sys",
"log",
"objc",
"objc-foundation",
"objc_id",
"raw-window-handle",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows-sys 0.48.0",
]
[[package]]
name = "rfd"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8af382a047821a08aa6bfc09ab0d80ff48d45d8726f7cd8e44891f7cb4a4278e"
dependencies = [
"ashpd 0.9.1",
"ashpd",
"block2",
"glib-sys",
"gobject-sys",
@@ -7273,9 +7233,9 @@ dependencies = [
[[package]]
name = "tao"
version = "0.30.0"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a93f2c6b8fdaeb7f417bda89b5bc767999745c3052969664ae1fa65892deb7e"
checksum = "82e7ede56f9ef03a0bb384c7b2bed4f3985ee7f3f79ec887c50d8466eec21096"
dependencies = [
"bitflags 2.6.0",
"cocoa",
@@ -7513,7 +7473,7 @@ source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#6bf1bd8d
dependencies = [
"log",
"raw-window-handle",
"rfd 0.15.0",
"rfd",
"serde",
"serde_json",
"tauri",
@@ -8458,9 +8418,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.1.13"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unsafe-libyaml"

View File

@@ -1,6 +1,6 @@
[target.aarch64-unknown-linux-gnu]
# dockerfile = "./manifest/docker/ubuntu-22.04-aarch64/Dockerfile"
image = "ghcr.io/libnyanpasu/builder-ubuntu-22.04-aarch64:latest"
image = "ghcr.io/libnyanpasu/builder-debian-trixie-aarch64:latest"
# pre-build = [
# "dpkg --add-architecture $CROSS_DEB_ARCH",
# """echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe multiverse" | tee /etc/apt/sources.list && \
@@ -25,7 +25,7 @@ image = "ghcr.io/libnyanpasu/builder-ubuntu-22.04-aarch64:latest"
# ]
[target.armv7-unknown-linux-gnueabihf]
image = "ghcr.io/libnyanpasu/builder-ubuntu-22.04-armhf:latest"
image = "ghcr.io/libnyanpasu/builder-debian-trixie-armhf:latest"
[target.armv7-unknown-linux-gnueabi]
image = "ghcr.io/libnyanpasu/builder-debian-trixie-armel:latest"

View File

@@ -69,7 +69,7 @@ runas = { git = "https://github.com/libnyanpasu/rust-runas.git" }
backon = { version = "1.0.1", features = ["tokio-sleep"] }
rust-i18n = "3"
adler = "1.0.2"
rfd = { version = "0.14", default-features = false, features = [
rfd = { version = "0.15", default-features = false, features = [
"tokio",
"gtk3",
"common-controls-v6",
@@ -155,8 +155,15 @@ tauri-plugin-global-shortcut = { git = "https://github.com/tauri-apps/plugins-wo
openssl = { version = "0.10", features = ["vendored"] }
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.26.0"
objc = "0.2.7"
objc2 = "0.5.2"
objc2-app-kit = { version = "0.2.2", features = [
"NSApplication",
"NSResponder",
"NSRunningApplication",
"NSWindow",
"NSView",
] }
objc2-foundation = { version = "0.2.2", features = ["NSGeometry"] }
[target.'cfg(windows)'.dependencies]
deelevate = "0.2.0"

View File

@@ -0,0 +1,20 @@
{
"$schema": "../../../node_modules/@tauri-apps/cli/config.schema.json",
"bundle": {
"windows": {
"webviewInstallMode": {
"type": "fixedRuntime",
"path": "SHOULD_BE_REPLACED_WITH_THE_PATH_TO_THE_FIXED_WEBVIEW"
}
}
},
"plugins": {
"updater": {
"endpoints": [
"https://mirror.ghproxy.com/https://github.com/LibNyanpasu/clash-nyanpasu/releases/download/updater/update-fixed-webview-proxy.json",
"https://gh-proxy.com/https://github.com/LibNyanpasu/clash-nyanpasu/releases/download/updater/update-fixed-webview-proxy.json",
"https://github.com/LibNyanpasu/clash-nyanpasu/releases/download/updater/update-fixed-webview.json"
]
}
}
}

View File

@@ -22,6 +22,8 @@ pub enum ClashCore {
Mihomo,
#[serde(rename = "mihomo-alpha")]
MihomoAlpha,
#[serde(rename = "clash-rs-alpha")]
ClashRsAlpha,
}
impl Default for ClashCore {
@@ -40,6 +42,7 @@ impl From<ClashCore> for String {
ClashCore::ClashRs => "clash-rs".into(),
ClashCore::Mihomo => "mihomo".into(),
ClashCore::MihomoAlpha => "mihomo-alpha".into(),
ClashCore::ClashRsAlpha => "clash-rs-alpha".into(),
}
}
}
@@ -51,6 +54,7 @@ impl std::fmt::Display for ClashCore {
ClashCore::ClashRs => write!(f, "clash-rs"),
ClashCore::Mihomo => write!(f, "mihomo"),
ClashCore::MihomoAlpha => write!(f, "mihomo-alpha"),
ClashCore::ClashRsAlpha => write!(f, "clash-rs-alpha"),
}
}
}
@@ -70,6 +74,9 @@ impl From<&ClashCore> for nyanpasu_utils::core::CoreType {
ClashCore::MihomoAlpha => nyanpasu_utils::core::CoreType::Clash(
nyanpasu_utils::core::ClashCoreType::MihomoAlpha,
),
ClashCore::ClashRsAlpha => nyanpasu_utils::core::CoreType::Clash(
nyanpasu_utils::core::ClashCoreType::ClashRustAlpha,
),
}
}
}
@@ -82,6 +89,7 @@ impl TryFrom<&nyanpasu_utils::core::CoreType> for ClashCore {
nyanpasu_utils::core::CoreType::Clash(clash) => match clash {
nyanpasu_utils::core::ClashCoreType::ClashPremium => Ok(ClashCore::ClashPremium),
nyanpasu_utils::core::ClashCoreType::ClashRust => Ok(ClashCore::ClashRs),
nyanpasu_utils::core::ClashCoreType::ClashRustAlpha => Ok(ClashCore::ClashRsAlpha),
nyanpasu_utils::core::ClashCoreType::Mihomo => Ok(ClashCore::Mihomo),
nyanpasu_utils::core::ClashCoreType::MihomoAlpha => Ok(ClashCore::MihomoAlpha),
},

View File

@@ -1,6 +1,6 @@
use crate::utils::dirs::{app_config_dir, app_data_dir, app_install_dir};
use runas::Command as RunasCommand;
use std::{borrow::Cow, ffi::OsString};
use std::ffi::OsString;
use super::SERVICE_PATH;

View File

@@ -9,7 +9,7 @@ use crate::core::{
tasks::task::Task,
};
use log::debug;
use redb::{ReadableTable, TableDefinition};
use redb::ReadableTable;
use std::{
str,
sync::{Arc, OnceLock},

View File

@@ -1,4 +1,4 @@
use std::{borrow::Cow, sync::atomic::AtomicU16};
use std::borrow::Cow;
use crate::{
config::{nyanpasu::ClashCore, Config},
@@ -21,6 +21,9 @@ pub mod proxies;
pub use self::icon::on_scale_factor_changed;
use self::proxies::SystemTrayMenuProxiesExt;
#[cfg(target_os = "linux")]
use std::sync::atomic::AtomicU16;
struct TrayState<R: Runtime> {
menu: Mutex<Menu<R>>,
}

View File

@@ -49,6 +49,7 @@ pub struct ManifestVersionLatest {
mihomo: String,
mihomo_alpha: String,
clash_rs: String,
clash_rs_alpha: String,
clash_premium: String,
}
@@ -57,6 +58,7 @@ pub struct ArchTemplate {
mihomo: HashMap<String, String>,
mihomo_alpha: HashMap<String, String>,
clash_rs: HashMap<String, String>,
clash_rs_alpha: HashMap<String, String>,
clash_premium: HashMap<String, String>,
}
@@ -77,6 +79,7 @@ impl Default for ManifestVersionLatest {
mihomo: "".to_string(),
mihomo_alpha: "".to_string(),
clash_rs: "".to_string(),
clash_rs_alpha: "".to_string(),
clash_premium: "".to_string(),
}
}
@@ -118,6 +121,14 @@ impl ManifestVersion {
.replace("{}", &self.latest.clash_rs),
CoreTypeMeta::ClashRs(self.latest.clash_rs.clone()),
)),
ClashCore::ClashRsAlpha => Some((
self.arch_template
.clash_rs_alpha
.get(arch)?
.clone()
.replace("{}", &self.latest.clash_rs_alpha),
CoreTypeMeta::ClashRs(self.latest.clash_rs_alpha.clone()),
)),
}
}
}
@@ -162,11 +173,15 @@ impl UpdaterManager {
let mirror = self.get_mirror().unwrap();
let latest = self.get_latest_version_manifest(&mirror);
let mihomo_alpha_version = self.get_mihomo_alpha_version();
let (latest, mihomo_alpha_version) = join!(latest, mihomo_alpha_version);
let clash_rs_alpha_version = self.get_clash_rs_alpha_version();
let (latest, mihomo_alpha_version, clash_rs_alpha_version) =
join!(latest, mihomo_alpha_version, clash_rs_alpha_version);
log::debug!("latest version: {:?}", latest);
self.manifest_version = latest?;
log::debug!("mihomo alpha version: {:?}", mihomo_alpha_version);
self.manifest_version.latest.mihomo_alpha = mihomo_alpha_version?;
log::debug!("clash rs alpha version: {:?}", clash_rs_alpha_version);
self.manifest_version.latest.clash_rs_alpha = clash_rs_alpha_version?;
Ok(())
}
@@ -217,6 +232,30 @@ impl UpdaterManager {
Ok(res.text().await?.trim().to_string())
}
async fn get_clash_rs_alpha_version(&self) -> Result<String> {
self.mirror_speed_test().await?;
let mirror = self.get_mirror().unwrap();
let url = crate::utils::candy::parse_gh_url(
&mirror,
"/Watfaq/clash-rs/releases/download/latest/version.txt",
)?;
let res = self.client.get(url).send().await?;
let status_code = res.status();
if !status_code.is_success() {
anyhow::bail!(
"failed to get clash rs alpha version: response status is {}, expected 200",
status_code
);
}
let res = res.text().await?;
let version = res
.trim()
.split(' ')
.last()
.ok_or(anyhow!("no version found"))?;
Ok(version.to_string())
}
pub async fn update_core(&mut self, core_type: &ClashCore) -> Result<usize> {
self.mirror_speed_test().await?;
let (artifact, tag) = self

View File

@@ -19,7 +19,7 @@ pub(super) fn get_arch() -> anyhow::Result<&'static str> {
("armel", "linux") => Ok("linux-armv7"),
("aarch64", "macos") => Ok("darwin-arm64"),
("aarch64", "linux") => Ok("linux-aarch64"),
// ("aarch64", "windows") => Ok("windows-arm64"),
("aarch64", "windows") => Ok("windows-arm64"),
_ => anyhow::bail!("unsupported platform"),
}
}
@@ -29,6 +29,7 @@ pub(super) enum CoreTypeMeta {
Mihomo(String),
MihomoAlpha,
ClashRs(String),
ClashRsAlpha,
}
pub(super) fn get_download_path(core_type: CoreTypeMeta, artifact: &str) -> String {
@@ -43,6 +44,9 @@ pub(super) fn get_download_path(core_type: CoreTypeMeta, artifact: &str) -> Stri
CoreTypeMeta::ClashRs(tag) => {
format!("Watfaq/clash-rs/releases/download/{}/{}", tag, artifact)
}
CoreTypeMeta::ClashRsAlpha => {
format!("Watfaq/clash-rs/releases/download/latest/{}", artifact)
}
CoreTypeMeta::ClashPremium(tag) => format!(
"zhongfly/Clash-premium-backup/releases/download/{}/{}",
tag, artifact

View File

@@ -4,14 +4,6 @@
windows_subsystem = "windows"
)]
#[cfg(target_os = "macos")]
#[macro_use]
extern crate cocoa;
#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;
mod cmds;
mod config;
mod consts;
@@ -28,7 +20,6 @@ use crate::{
utils::{init, resolve},
};
use tauri::Emitter;
use tauri_plugin_shell::ShellExt;
use utils::resolve::{is_window_opened, reset_window_open_counter};
rust_i18n::i18n!("../../locales");
@@ -338,9 +329,9 @@ pub fn run() -> std::io::Result<()> {
reset_window_open_counter();
let _ = resolve::save_window_state(app_handle, true);
#[cfg(target_os = "macos")]
unsafe {
log_err!(app_handle.run_on_main_thread(|| {
crate::utils::dock::macos::hide_dock_icon();
}
}));
}
tauri::WindowEvent::Moved(_) | tauri::WindowEvent::Resized(_) => {
log::debug!(target: "app", "window moved or resized");

View File

@@ -1,23 +1,22 @@
#[cfg(target_os = "macos")]
pub mod macos {
extern crate cocoa;
extern crate objc;
use cocoa::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy};
use objc::runtime::YES;
pub unsafe fn show_dock_icon() {
let app = NSApp();
app.setActivationPolicy_(
NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
);
app.activateIgnoringOtherApps_(YES);
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy};
use objc2_foundation::MainThreadMarker;
use std::cell::Cell;
thread_local! {
static MARK: Cell<MainThreadMarker> = Cell::new(MainThreadMarker::new().unwrap());
}
pub unsafe fn hide_dock_icon() {
let app = NSApp();
app.setActivationPolicy_(
NSApplicationActivationPolicy::NSApplicationActivationPolicyAccessory,
);
pub fn show_dock_icon() {
let app = NSApplication::sharedApplication(MARK.get());
app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
unsafe {
app.activate();
}
}
pub fn hide_dock_icon() {
let app = NSApplication::sharedApplication(MARK.get());
app.setActivationPolicy(NSApplicationActivationPolicy::Accessory);
}
}

View File

@@ -99,14 +99,32 @@ pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
pub fn open_file(app: tauri::AppHandle, path: PathBuf) -> Result<()> {
#[cfg(target_os = "macos")]
let code = "Visual Studio Code";
#[cfg(not(target_os = "macos"))]
#[cfg(windows)]
let code = "code.cmd";
#[cfg(all(not(windows), not(target_os = "macos")))]
let code = "code";
let shell = app.shell();
trace_err!(
match which::which(code) {
Ok(_) => crate::utils::open::with(path, code),
Ok(code_path) => {
log::debug!(target: "app", "find VScode `{}`", code_path.display());
#[cfg(not(windows))]
{
crate::utils::open::with(path, code)
}
#[cfg(windows)]
{
use std::ffi::OsString;
let mut buf = OsString::with_capacity(path.as_os_str().len() + 2);
buf.push("\"");
buf.push(path.as_os_str());
buf.push("\"");
open::with_detached(buf, code)
}
}
Err(err) => {
log::error!(target: "app", "Can't find VScode `{err}`");
// default open

View File

@@ -32,34 +32,39 @@ pub fn reset_window_open_counter() {
}
#[cfg(target_os = "macos")]
fn set_window_controls_pos(window: cocoa::base::id, x: f64, y: f64) {
use cocoa::{
appkit::{NSView, NSWindow, NSWindowButton},
foundation::NSRect,
};
fn set_window_controls_pos(window: objc2::rc::Retained<objc2_app_kit::NSWindow>, x: f64, y: f64) {
use objc2_app_kit::NSWindowButton;
use objc2_foundation::NSRect;
let close = window
.standardWindowButton(NSWindowButton::NSWindowCloseButton)
.unwrap();
let miniaturize = window
.standardWindowButton(NSWindowButton::NSWindowMiniaturizeButton)
.unwrap();
let zoom = window
.standardWindowButton(NSWindowButton::NSWindowZoomButton)
.unwrap();
let title_bar_container_view = unsafe { close.superview().unwrap().superview().unwrap() };
let close_rect = close.frame();
let button_height = close_rect.size.height;
let title_bar_frame_height = button_height + y;
let mut title_bar_rect = title_bar_container_view.frame();
title_bar_rect.size.height = title_bar_frame_height;
title_bar_rect.origin.y = window.frame().size.height - title_bar_frame_height;
unsafe {
let close = window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
let miniaturize = window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
let zoom = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
title_bar_container_view.setFrame(title_bar_rect);
}
let title_bar_container_view = close.superview().superview();
let space_between = miniaturize.frame().origin.x - close.frame().origin.x;
let window_buttons = vec![close, miniaturize, zoom];
let close_rect: NSRect = msg_send![close, frame];
let button_height = close_rect.size.height;
let title_bar_frame_height = button_height + y;
let mut title_bar_rect = NSView::frame(title_bar_container_view);
title_bar_rect.size.height = title_bar_frame_height;
title_bar_rect.origin.y = NSView::frame(window).size.height - title_bar_frame_height;
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
let window_buttons = vec![close, miniaturize, zoom];
let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
for (i, button) in window_buttons.into_iter().enumerate() {
let mut rect: NSRect = NSView::frame(button);
rect.origin.x = x + (i as f64 * space_between);
for (i, button) in window_buttons.into_iter().enumerate() {
let mut rect: NSRect = button.frame();
rect.origin.x = x + (i as f64 * space_between);
unsafe {
button.setFrameOrigin(rect.origin);
}
}
@@ -86,13 +91,15 @@ pub fn find_unused_port() -> Result<u16> {
pub fn resolve_setup(app: &mut App) {
#[cfg(target_os = "macos")]
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
#[cfg(target_os = "macos")]
let app_handle = app.app_handle().clone();
app.listen("react_app_mounted", move |_| {
tracing::debug!("Frontend React App is mounted, reset open window counter");
reset_window_open_counter();
#[cfg(target_os = "macos")]
unsafe {
log_err!(app_handle.run_on_main_thread(move || {
crate::utils::dock::macos::show_dock_icon();
}
}));
});
handle::Handle::global().init(app.app_handle().clone());
@@ -250,14 +257,24 @@ pub fn create_window(app_handle: &AppHandle) {
#[cfg(target_os = "macos")]
fn set_controls_and_log_error(app_handle: &tauri::AppHandle, window_name: &str) {
use objc2::{rc::Retained, runtime::NSObject};
use objc2_app_kit::NSWindow;
match app_handle
.get_webview_window(window_name)
.unwrap()
.ns_window()
{
Ok(raw_window) => {
let window_id: cocoa::base::id = raw_window as _;
set_window_controls_pos(window_id, 26.0, 26.0);
let obj: Option<Retained<NSWindow>> =
unsafe { Retained::from_raw(raw_window as *mut NSWindow) };
match obj {
Some(window) => {
set_window_controls_pos(window, 26.0, 26.0);
}
None => {
log::error!(target: "app", "failed to get ns_window");
}
}
}
Err(err) => {
log::error!(target: "app", "failed to get ns_window, {err}");
@@ -447,7 +464,7 @@ pub async fn resolve_core_version(app_handle: &AppHandle, core_type: &ClashCore)
ClashCore::ClashPremium | ClashCore::Mihomo | ClashCore::MihomoAlpha => {
shell.sidecar(core)?.args(["-v"])
}
ClashCore::ClashRs => shell.sidecar(core)?.args(["-V"]),
ClashCore::ClashRs | ClashCore::ClashRsAlpha => shell.sidecar(core)?.args(["-V"]),
};
let out = cmd.output().await?;
if !out.status.success() {

View File

@@ -9,7 +9,6 @@
"digestAlgorithm": "sha256",
"timestampUrl": "",
"webviewInstallMode": {
"silent": true,
"type": "embedBootstrapper"
},
"wix": {

View File

@@ -18,6 +18,6 @@
"swr": "2.2.5"
},
"devDependencies": {
"@types/react": "18.3.7"
"@types/react": "18.3.8"
}
}

View File

@@ -15,6 +15,7 @@ export const VALID_CORE: Core[] = [
{ name: "Mihomo", core: "mihomo" },
{ name: "Mihomo Alpha", core: "mihomo-alpha" },
{ name: "Clash Rust", core: "clash-rs" },
{ name: "Clash Rust Alpha", core: "clash-rs-alpha" },
];
export const fetchCoreVersion = async () => {

View File

@@ -3,7 +3,12 @@ import { Clash } from "./clash";
export interface VergeConfig {
app_log_level?: "trace" | "debug" | "info" | "warn" | "error" | string;
language?: string;
clash_core?: "mihomo" | "mihomo-alpha" | "clash-rs" | "clash";
clash_core?:
| "mihomo"
| "mihomo-alpha"
| "clash-rs"
| "clash-rs-alpha"
| "clash";
theme_mode?: "light" | "dark" | "system";
theme_blur?: boolean;
traffic_graph?: boolean;

View File

@@ -30,10 +30,10 @@
"dayjs": "1.11.13",
"framer-motion": "12.0.0-alpha.1",
"i18next": "23.15.1",
"jotai": "2.9.3",
"jotai": "2.10.0",
"json-schema": "0.4.0",
"material-react-table": "3.0.1",
"monaco-editor": "0.51.0",
"monaco-editor": "0.52.0",
"mui-color-input": "4.0.0",
"react": "rc",
"react-dom": "rc",
@@ -66,7 +66,7 @@
"@tauri-apps/plugin-process": "2.0.0-rc.1",
"@tauri-apps/plugin-shell": "2.0.0-rc.1",
"@tauri-apps/plugin-updater": "2.0.0-rc.2",
"@types/react": "18.3.7",
"@types/react": "18.3.8",
"@types/react-dom": "18.3.0",
"@types/validator": "13.12.2",
"@vitejs/plugin-react": "4.3.1",
@@ -75,8 +75,8 @@
"meta-json-schema": "1.18.8",
"monaco-yaml": "5.2.2",
"nanoid": "5.0.7",
"sass": "1.79.1",
"shiki": "1.17.7",
"sass": "1.79.2",
"shiki": "1.18.0",
"tailwindcss-textshadow": "2.1.3",
"unplugin-auto-import": "0.18.3",
"unplugin-icons": "0.19.3",

View File

@@ -31,7 +31,8 @@ export const getImage = (core: ClashCore) => {
return ClashMeta;
}
case "clash-rs": {
case "clash-rs":
case "clash-rs-alpha": {
return ClashRs;
}

View File

@@ -24,7 +24,7 @@
"@radix-ui/react-scroll-area": "1.1.0",
"@tauri-apps/api": "2.0.0-rc.5",
"@types/d3": "7.4.3",
"@types/react": "18.3.7",
"@types/react": "18.3.8",
"@vitejs/plugin-react": "4.3.1",
"ahooks": "3.8.1",
"d3": "7.9.0",
@@ -42,7 +42,7 @@
"@types/d3-interpolate-path": "2.0.3",
"clsx": "2.1.1",
"d3-interpolate-path": "2.3.0",
"sass": "1.79.1",
"sass": "1.79.2",
"tailwind-merge": "2.5.2",
"typescript-plugin-css-modules": "5.1.0",
"vite-plugin-dts": "4.2.1"

View File

@@ -2,14 +2,16 @@
"manifest_version": 1,
"latest": {
"mihomo": "v1.18.8",
"mihomo_alpha": "alpha-3676d1b",
"clash_rs": "v0.3.2",
"clash_premium": "2023-09-05-gdcc8d87"
"mihomo_alpha": "alpha-a08aa10",
"clash_rs": "v0.4.0",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.4.0"
},
"arch_template": {
"mihomo": {
"windows-i386": "mihomo-windows-386-{}.zip",
"windows-x86_64": "mihomo-windows-amd64-compatible-{}.zip",
"windows-arm64": "mihomo-windows-arm64-{}.zip",
"linux-aarch64": "mihomo-linux-arm64-{}.gz",
"linux-amd64": "mihomo-linux-amd64-compatible-{}.gz",
"linux-i386": "mihomo-linux-386-{}.gz",
@@ -21,6 +23,7 @@
"mihomo_alpha": {
"windows-i386": "mihomo-windows-386-{}.zip",
"windows-x86_64": "mihomo-windows-amd64-compatible-{}.zip",
"windows-arm64": "mihomo-windows-arm64-{}.zip",
"linux-aarch64": "mihomo-linux-arm64-{}.gz",
"linux-amd64": "mihomo-linux-amd64-compatible-{}.gz",
"linux-i386": "mihomo-linux-386-{}.gz",
@@ -32,6 +35,7 @@
"clash_rs": {
"windows-i386": "clash-i686-pc-windows-msvc-static-crt.exe",
"windows-x86_64": "clash-x86_64-pc-windows-msvc.exe",
"windows-arm64": "clash-aarch64-pc-windows-msvc.exe",
"linux-aarch64": "clash-aarch64-unknown-linux-gnu-static-crt",
"linux-amd64": "clash-x86_64-unknown-linux-gnu-static-crt",
"linux-i386": "clash-i686-unknown-linux-gnu-static-crt",
@@ -43,6 +47,7 @@
"clash_premium": {
"windows-i386": "clash-windows-386-n{}.zip",
"windows-x86_64": "clash-windows-amd64-n{}.zip",
"windows-arm64": "clash-windows-arm64-n{}.zip",
"linux-aarch64": "clash-linux-arm64-n{}.gz",
"linux-amd64": "clash-linux-amd64-n{}.gz",
"linux-i386": "clash-linux-386-n{}.gz",
@@ -50,7 +55,19 @@
"darwin-x64": "clash-darwin-amd64-n{}.gz",
"linux-armv7": "clash-linux-armv5-n{}.gz",
"linux-armv7hf": "clash-linux-armv7-n{}.gz"
},
"clash_rs_alpha": {
"windows-i386": "clash-i686-pc-windows-msvc-static-crt.exe",
"windows-x86_64": "clash-x86_64-pc-windows-msvc.exe",
"windows-arm64": "clash-aarch64-pc-windows-msvc.exe",
"linux-aarch64": "clash-aarch64-unknown-linux-gnu-static-crt",
"linux-amd64": "clash-x86_64-unknown-linux-gnu-static-crt",
"linux-i386": "clash-i686-unknown-linux-gnu-static-crt",
"darwin-arm64": "clash-aarch64-apple-darwin",
"darwin-x64": "clash-x86_64-apple-darwin",
"linux-armv7": "clash-armv7-unknown-linux-gnueabi-static-crt",
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
"updated_at": "2024-09-18T22:20:35.900Z"
"updated_at": "2024-09-20T02:46:22.198Z"
}

View File

@@ -80,7 +80,7 @@
"eslint-plugin-prettier": "5.2.1",
"eslint-plugin-promise": "7.1.0",
"eslint-plugin-react": "7.36.1",
"eslint-plugin-react-compiler": "0.0.0-experimental-7670337-20240918",
"eslint-plugin-react-compiler": "0.0.0-experimental-92aaa43-20240919",
"eslint-plugin-react-hooks": "4.6.2",
"knip": "5.30.2",
"lint-staged": "15.2.10",
@@ -104,7 +104,7 @@
"tsx": "4.19.1",
"typescript": "5.6.2"
},
"packageManager": "pnpm@9.10.0+sha512.73a29afa36a0d092ece5271de5177ecbf8318d454ecd701343131b8ebc0c1a91c487da46ab77c8e596d6acf1461e3594ced4becedf8921b074fbd8653ed7051c",
"packageManager": "pnpm@9.11.0+sha512.0a203ffaed5a3f63242cd064c8fb5892366c103e328079318f78062f24ea8c9d50bc6a47aa3567cabefd824d170e78fa2745ed1f16b132e16436146b7688f19b",
"engines": {
"node": "22.9.0"
},

File diff suppressed because it is too large Load Diff

View File

@@ -44,6 +44,7 @@ const tasks: {
{ name: "mihomo", func: () => resolve.clashMeta(), retry: 5 },
{ name: "mihomo-alpha", func: () => resolve.clashMetaAlpha(), retry: 5 },
{ name: "clash-rs", func: () => resolve.clashRust(), retry: 5 },
{ name: "clash-rs-alpha", func: () => resolve.clashRustAlpha(), retry: 5 },
{ name: "wintun", func: () => resolve.wintun(), retry: 5, winOnly: true },
{
name: "nyanpasu-service",

View File

@@ -5,6 +5,7 @@ import { consola } from "./utils/logger";
import {
resolveClashPremium,
resolveClashRs,
resolveClashRsAlpha,
resolveMihomo,
resolveMihomoAlpha,
} from "./utils/manifest";
@@ -17,6 +18,7 @@ export async function generateLatestVersion() {
resolveMihomoAlpha,
resolveClashRs,
resolveClashPremium,
resolveClashRsAlpha,
];
consola.start("Resolving latest versions");

View File

@@ -6,3 +6,11 @@ export const CLASH_RS_MANIFEST: ClashManifest = {
VERSION: versionManifest.latest.clash_rs,
ARCH_MAPPING: versionManifest.arch_template.clash_rs,
};
export const CLASH_RS_ALPHA_MANIFEST: ClashManifest = {
VERSION_URL:
"https://github.com/Watfaq/clash-rs/releases/download/latest/version.txt",
URL_PREFIX: "https://github.com/Watfaq/clash-rs/releases/download/latest",
VERSION: versionManifest.latest.clash_rs_alpha,
ARCH_MAPPING: versionManifest.arch_template.clash_rs_alpha,
};

View File

@@ -3,9 +3,11 @@ import AdmZip from "adm-zip";
import fs from "fs-extra";
import { context, getOctokit } from "@actions/github";
import packageJson from "../package.json";
import { TAURI_APP_DIR } from "./utils/env";
import { colorize, consola } from "./utils/logger";
const RUST_ARCH = process.env.RUST_ARCH || "x86_64";
const fixedWebview = process.argv.includes("--fixed-webview");
/// Script for ci
/// 打包绿色版/便携版 (only Windows)
@@ -38,12 +40,27 @@ async function resolvePortable() {
zip.addLocalFile(path.join(buildDir, "mihomo-alpha.exe"));
zip.addLocalFile(path.join(buildDir, "nyanpasu-service.exe"));
zip.addLocalFile(path.join(buildDir, "clash-rs.exe"));
zip.addLocalFile(path.join(buildDir, "clash-rs-alpha.exe"));
zip.addLocalFolder(path.join(buildDir, "resources"), "resources");
if (fixedWebview) {
const webviewPath = (await fs.readdir(TAURI_APP_DIR)).find((file) =>
file.includes("WebView2"),
);
if (!webviewPath) {
throw new Error("WebView2 runtime not found");
}
zip.addLocalFolder(
path.join(TAURI_APP_DIR, webviewPath),
path.basename(webviewPath),
);
}
zip.addLocalFolder(configDir, ".config");
const { version } = packageJson;
const zipFile = `Clash.Nyanpasu_${version}_${RUST_ARCH}_portable.zip`;
const zipFile = `Clash.Nyanpasu_${version}_${RUST_ARCH}${fixedWebview ? "_fixed-webview" : ""}_portable.zip`;
zip.writeZip(zipFile);
consola.success("create portable zip successfully");

View File

@@ -2,7 +2,11 @@ import { execSync } from "child_process";
import path from "node:path";
import fs from "fs-extra";
import { merge } from "lodash-es";
import { cwd, TAURI_APP_DIR } from "./utils/env";
import {
cwd,
TAURI_APP_DIR,
TAURI_FIXED_WEBVIEW2_CONFIG_OVERRIDE_PATH,
} from "./utils/env";
import { consola } from "./utils/logger";
const TAURI_DEV_APP_CONF_PATH = path.join(
@@ -24,15 +28,34 @@ const NYANPASU_PACKAGE_JSON_PATH = path.join(
const isNSIS = process.argv.includes("--nsis"); // only build nsis
const isMSI = process.argv.includes("--msi"); // only build msi
const fixedWebview = process.argv.includes("--fixed-webview");
async function main() {
consola.debug("Read config...");
const tauriAppConf = await fs.readJSON(TAURI_APP_CONF);
const tauriAppOverrides = await fs.readJSON(TAURI_DEV_APP_OVERRIDES_PATH);
const tauriConf = merge(tauriAppConf, tauriAppOverrides);
let tauriConf = merge(tauriAppConf, tauriAppOverrides);
const packageJson = await fs.readJSON(NYANPASU_PACKAGE_JSON_PATH);
const rootPackageJson = await fs.readJSON(ROOT_PACKAGE_JSON_PATH);
// const wxsFile = await fs.readFile(WXS_PATH, "utf-8");
if (fixedWebview) {
const fixedWebview2Config = await fs.readJSON(
TAURI_FIXED_WEBVIEW2_CONFIG_OVERRIDE_PATH,
);
const webviewPath = (await fs.readdir(TAURI_APP_DIR)).find((file) =>
file.includes("WebView2"),
);
if (!webviewPath) {
throw new Error("WebView2 runtime not found");
}
tauriConf = merge(tauriConf, fixedWebview2Config);
delete tauriConf.bundle.windows.webviewInstallMode.silent;
tauriConf.bundle.windows.webviewInstallMode.path = `./${path.basename(webviewPath)}`;
tauriConf.plugins.updater.endpoints =
tauriConf.plugins.updater.endpoints.map((o: string) =>
o.replace("update-", "update-nightly-"),
);
}
if (isNSIS) {
tauriConf.bundle.targets = ["nsis"];

View File

@@ -1,6 +1,11 @@
import path from "node:path";
import fs from "fs-extra";
import { cwd, TAURI_APP_DIR } from "./utils/env";
import { merge } from "lodash-es";
import {
cwd,
TAURI_APP_DIR,
TAURI_FIXED_WEBVIEW2_CONFIG_OVERRIDE_PATH,
} from "./utils/env";
import { consola } from "./utils/logger";
const TAURI_APP_CONF = path.join(TAURI_APP_DIR, "tauri.conf.json");
@@ -14,19 +19,35 @@ const PACKAGE_JSON_PATH = path.join(cwd, "package.json");
// const WXS_PATH = path.join(TAURI_APP_DIR, "templates", "nightly.wxs");
const isNSIS = process.argv.includes("--nsis"); // only build nsis
const fixedWebview = process.argv.includes("--fixed-webview");
async function main() {
consola.debug("Read config...");
const tauriAppConf = await fs.readJSON(TAURI_APP_CONF);
// const tauriAppOverrides = await fs.readJSON(TAURI_DEV_APP_OVERRIDES_PATH);
// const tauriConf = merge(tauriAppConf, tauriAppOverrides);
const tauriConf = tauriAppConf;
let tauriConf = tauriAppConf;
// const wxsFile = await fs.readFile(WXS_PATH, "utf-8");
// if (isNSIS) {
// tauriConf.tauri.bundle.targets = ["nsis", "updater"];
// }
if (fixedWebview) {
const fixedWebview2Config = await fs.readJSON(
TAURI_FIXED_WEBVIEW2_CONFIG_OVERRIDE_PATH,
);
const webviewPath = (await fs.readdir(TAURI_APP_DIR)).find((file) =>
file.includes("WebView2"),
);
if (!webviewPath) {
throw new Error("WebView2 runtime not found");
}
tauriConf = merge(tauriConf, fixedWebview2Config);
delete tauriConf.bundle.windows.webviewInstallMode.silent;
tauriConf.bundle.windows.webviewInstallMode.path = `./${path.basename(webviewPath)}`;
}
consola.debug("Write tauri version to tauri.conf.json");
await fs.writeJSON(TAURI_APP_CONF, tauriConf, { spaces: 2 });
consola.debug("tauri.conf.json updated");

View File

@@ -38,6 +38,8 @@ if (!process.env.GITHUB_TOKEN) {
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const WORKFLOW_RUN_ID = process.env.WORKFLOW_RUN_ID;
const resourceFormats = [
".exe",
"portable.zip",
@@ -101,6 +103,29 @@ const repoInfo = {
consola.log(`existed ${item}:`, existsSync(item));
});
if (!nightlyBuild) {
await client.sendMessage(TELEGRAM_TO, {
message: array2text([
`Clash Nyanpasu ${version} Released!`,
"",
"Check out on GitHub:",
` - https://github.com/LibNyanpasu/clash-nyanpasu/releases/tag/v${version}`,
]),
});
consola.success("Send release message to telegram successfully");
} else {
await client.sendMessage(TELEGRAM_TO_NIGHTLY, {
message: array2text([
`Clash Nyanpasu Nightly Build ${GIT_SHORT_HASH} released!`,
"",
"Could be unstable, use at your own risk. Download at:",
`- https://github.com/libnyanpasu/clash-nyanpasu/actions/runs/${WORKFLOW_RUN_ID}`,
"",
"You could also waiting for the telegram bot to upload the binaries, although it may take a while or even fail.",
]),
});
}
consola.start("Staring upload tasks (nightly)");
// upload windows binary
@@ -151,21 +176,6 @@ const repoInfo = {
consola.success("Upload finished (nightly)");
if (!nightlyBuild) {
consola.start("Staring upload tasks (release)");
await client.sendMessage(TELEGRAM_TO, {
message: array2text([
`Clash Nyanpasu ${version} Released!`,
"",
"Check out on GitHub:",
` - https://github.com/LibNyanpasu/clash-nyanpasu/releases/tag/v${version}`,
]),
});
consola.success("Upload finished (release)");
}
await client.disconnect();
process.exit();

View File

@@ -20,11 +20,9 @@ export interface BinInfo {
}
export enum SupportedArch {
// blocked by clash-rs
WindowsX86_32 = "windows-i386",
WindowsX86_64 = "windows-x86_64",
// blocked by clash-rs#212
// WindowsArm64 = "windows-arm64",
WindowsArm64 = "windows-arm64",
LinuxAarch64 = "linux-aarch64",
LinuxAmd64 = "linux-amd64",
LinuxI386 = "linux-i386",
@@ -38,6 +36,7 @@ export enum SupportedCore {
Mihomo = "mihomo",
MihomoAlpha = "mihomo_alpha",
ClashRs = "clash_rs",
ClashRsAlpha = "clash_rs_alpha",
ClashPremium = "clash_premium",
}

View File

@@ -9,6 +9,10 @@ import { consola } from "./utils/logger";
const UPDATE_TAG_NAME = "updater";
const UPDATE_JSON_FILE = "update-nightly.json";
const UPDATE_JSON_PROXY = "update-nightly-proxy.json";
const UPDATE_FIXED_WEBVIEW_FILE = "update-nightly-fixed-webview.json";
const UPDATE_FIXED_WEBVIEW_PROXY = "update-nightly-fixed-webview-proxy.json";
const isFixedWebview = process.argv.includes("--fixed-webview");
/// generate update.json
/// upload to update tag's release asset
@@ -57,34 +61,44 @@ async function resolveUpdater() {
const promises = latestPreRelease.assets.map(async (asset) => {
const { name, browser_download_url } = asset;
function isMatch(name: string, extension: string, arch: string) {
return (
name.endsWith(extension) &&
name.includes(arch) &&
(isFixedWebview
? name.includes("fixed-webview")
: !name.includes("fixed-webview"))
);
}
// win64 url
if (name.endsWith(".nsis.zip") && name.includes("x64")) {
if (isMatch(name, ".nsis.zip", "x64")) {
updateData.platforms.win64.url = browser_download_url;
updateData.platforms["windows-x86_64"].url = browser_download_url;
}
// win64 signature
if (name.endsWith(".nsis.zip.sig") && name.includes("x64")) {
if (isMatch(name, ".nsis.zip.sig", "x64")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.win64.signature = sig;
updateData.platforms["windows-x86_64"].signature = sig;
}
// win32 url
if (name.endsWith(".nsis.zip") && name.includes("x86")) {
if (isMatch(name, ".nsis.zip", "x86")) {
updateData.platforms["windows-i686"].url = browser_download_url;
}
// win32 signature
if (name.endsWith(".nsis.zip.sig") && name.includes("x86")) {
if (isMatch(name, ".nsis.zip.sig", "x86")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-i686"].signature = sig;
}
// win arm64 url
if (name.endsWith(".nsis.zip") && name.includes("arm64")) {
if (isMatch(name, ".nsis.zip", "arm64")) {
updateData.platforms["windows-aarch64"].url = browser_download_url;
}
// win arm64 signature
if (name.endsWith(".nsis.zip.sig") && name.includes("arm64")) {
if (isMatch(name, ".nsis.zip.sig", "arm64")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-aarch64"].signature = sig;
}
@@ -178,14 +192,22 @@ async function resolveUpdater() {
// delete the old assets
for (const asset of updateRelease.assets) {
if (asset.name === UPDATE_JSON_FILE) {
if (
isFixedWebview
? asset.name === UPDATE_FIXED_WEBVIEW_FILE
: asset.name === UPDATE_JSON_FILE
) {
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: asset.id,
});
}
if (asset.name === UPDATE_JSON_PROXY) {
if (
isFixedWebview
? asset.name === UPDATE_FIXED_WEBVIEW_PROXY
: asset.name === UPDATE_JSON_PROXY
) {
await github.rest.repos
.deleteReleaseAsset({ ...options, asset_id: asset.id })
.catch((err) => {
@@ -198,14 +220,14 @@ async function resolveUpdater() {
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: UPDATE_JSON_FILE,
name: isFixedWebview ? UPDATE_FIXED_WEBVIEW_FILE : UPDATE_JSON_FILE,
data: JSON.stringify(updateData, null, 2),
});
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: UPDATE_JSON_PROXY,
name: isFixedWebview ? UPDATE_FIXED_WEBVIEW_PROXY : UPDATE_JSON_PROXY,
data: JSON.stringify(updateDataNew, null, 2),
});
consola.success("updater files updated");

View File

@@ -7,8 +7,12 @@ import { colorize, consola } from "./utils/logger";
const UPDATE_TAG_NAME = "updater";
const UPDATE_JSON_FILE = "update.json";
const UPDATE_JSON_PROXY = "update-proxy.json";
const UPDATE_FIXED_WEBVIEW_FILE = "update-fixed-webview.json";
const UPDATE_FIXED_WEBVIEW_PROXY = "update-fixed-webview-proxy.json";
const UPDATE_RELEASE_BODY = process.env.RELEASE_BODY || "";
const isFixedWebview = process.argv.includes("--fixed-webview");
/// generate update.json
/// upload to update tag's release asset
async function resolveUpdater() {
@@ -65,34 +69,44 @@ async function resolveUpdater() {
const promises = latestRelease.assets.map(async (asset) => {
const { name, browser_download_url } = asset;
function isMatch(name: string, extension: string, arch: string) {
return (
name.endsWith(extension) &&
name.includes(arch) &&
(isFixedWebview
? name.includes("fixed-webview")
: !name.includes("fixed-webview"))
);
}
// win64 url
if (name.endsWith(".nsis.zip") && name.includes("x64")) {
if (isMatch(name, ".nsis.zip", "x64")) {
updateData.platforms.win64.url = browser_download_url;
updateData.platforms["windows-x86_64"].url = browser_download_url;
}
// win64 signature
if (name.endsWith(".nsis.zip.sig") && name.includes("x64")) {
if (isMatch(name, ".nsis.zip.sig", "x64")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.win64.signature = sig;
updateData.platforms["windows-x86_64"].signature = sig;
}
// win32 url
if (name.endsWith(".nsis.zip") && name.includes("x86")) {
if (isMatch(name, ".nsis.zip", "x86")) {
updateData.platforms["windows-i686"].url = browser_download_url;
}
// win32 signature
if (name.endsWith(".nsis.zip.sig") && name.includes("x86")) {
if (isMatch(name, ".nsis.zip.sig", "x86")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-i686"].signature = sig;
}
// win arm64 url
if (name.endsWith(".nsis.zip") && name.includes("arm64")) {
if (isMatch(name, ".nsis.zip", "arm64")) {
updateData.platforms["windows-aarch64"].url = browser_download_url;
}
// win arm64 signature
if (name.endsWith(".nsis.zip.sig") && name.includes("arm64")) {
if (isMatch(name, ".nsis.zip.sig", "arm64")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-aarch64"].signature = sig;
}
@@ -169,14 +183,22 @@ async function resolveUpdater() {
// delete the old assets
for (const asset of updateRelease.assets) {
if (asset.name === UPDATE_JSON_FILE) {
if (
isFixedWebview
? asset.name === UPDATE_FIXED_WEBVIEW_FILE
: asset.name === UPDATE_JSON_FILE
) {
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: asset.id,
});
}
if (asset.name === UPDATE_JSON_PROXY) {
if (
isFixedWebview
? asset.name === UPDATE_FIXED_WEBVIEW_PROXY
: asset.name === UPDATE_JSON_PROXY
) {
await github.rest.repos
.deleteReleaseAsset({ ...options, asset_id: asset.id })
.catch((err) => {
@@ -189,14 +211,14 @@ async function resolveUpdater() {
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: UPDATE_JSON_FILE,
name: isFixedWebview ? UPDATE_FIXED_WEBVIEW_FILE : UPDATE_JSON_FILE,
data: JSON.stringify(updateData, null, 2),
});
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: UPDATE_JSON_PROXY,
name: isFixedWebview ? UPDATE_FIXED_WEBVIEW_PROXY : UPDATE_JSON_PROXY,
data: JSON.stringify(updateDataNew, null, 2),
});
}

View File

@@ -2,6 +2,10 @@ import path from "path";
export const cwd = process.cwd();
export const TAURI_APP_DIR = path.join(cwd, "backend/tauri");
export const TAURI_FIXED_WEBVIEW2_CONFIG_OVERRIDE_PATH = path.join(
TAURI_APP_DIR,
"overrides/fixed-webview2.conf.json",
);
export const MANIFEST_DIR = path.join(cwd, "manifest");
export const GITHUB_PROXY = "https://mirror.ghproxy.com/";
export const GITHUB_TOKEN = process.env.GITHUB_TOKEN;

View File

@@ -26,7 +26,7 @@ export const resolveMihomo = async (): LatestVersionResolver => {
const archMapping: ArchMapping = {
[SupportedArch.WindowsX86_32]: "mihomo-windows-386-{}.zip",
[SupportedArch.WindowsX86_64]: "mihomo-windows-amd64-compatible-{}.zip",
// [SupportedArch.WindowsAarch64]: "mihomo-windows-arm64-{}.zip",
[SupportedArch.WindowsArm64]: "mihomo-windows-arm64-{}.zip",
[SupportedArch.LinuxAarch64]: "mihomo-linux-arm64-{}.gz",
[SupportedArch.LinuxAmd64]: "mihomo-linux-amd64-compatible-{}.gz",
[SupportedArch.LinuxI386]: "mihomo-linux-386-{}.gz",
@@ -55,7 +55,7 @@ export const resolveMihomoAlpha = async (): LatestVersionResolver => {
const archMapping: ArchMapping = {
[SupportedArch.WindowsX86_32]: "mihomo-windows-386-{}.zip",
[SupportedArch.WindowsX86_64]: "mihomo-windows-amd64-compatible-{}.zip",
// [SupportedArch.WindowsAarch64]: "mihomo-windows-arm64-{}.zip",
[SupportedArch.WindowsArm64]: "mihomo-windows-arm64-{}.zip",
[SupportedArch.LinuxAarch64]: "mihomo-linux-arm64-{}.gz",
[SupportedArch.LinuxAmd64]: "mihomo-linux-amd64-compatible-{}.gz",
[SupportedArch.LinuxI386]: "mihomo-linux-386-{}.gz",
@@ -85,7 +85,7 @@ export const resolveClashRs = async (): LatestVersionResolver => {
const archMapping: ArchMapping = {
[SupportedArch.WindowsX86_32]: "clash-i686-pc-windows-msvc-static-crt.exe",
[SupportedArch.WindowsX86_64]: "clash-x86_64-pc-windows-msvc.exe",
// [SupportedArch.WindowsAarch64]: "mihomo-windows-arm64-alpha-{}.zip",
[SupportedArch.WindowsArm64]: "clash-aarch64-pc-windows-msvc.exe",
[SupportedArch.LinuxAarch64]: "clash-aarch64-unknown-linux-gnu-static-crt",
[SupportedArch.LinuxAmd64]: "clash-x86_64-unknown-linux-gnu-static-crt",
[SupportedArch.LinuxI386]: "clash-i686-unknown-linux-gnu-static-crt",
@@ -102,6 +102,37 @@ export const resolveClashRs = async (): LatestVersionResolver => {
};
};
export const resolveClashRsAlpha = async (): LatestVersionResolver => {
const resp = await fetch(
"https://github.com/Watfaq/clash-rs/releases/download/latest/version.txt",
);
const alphaVersion = resp.ok
? (await resp.text()).trim().split(" ").pop()!
: "latest";
consola.debug(`clash-rs alpha latest release: ${alphaVersion}`);
const archMapping: ArchMapping = {
[SupportedArch.WindowsX86_32]: "clash-i686-pc-windows-msvc-static-crt.exe",
[SupportedArch.WindowsX86_64]: "clash-x86_64-pc-windows-msvc.exe",
[SupportedArch.WindowsArm64]: "clash-aarch64-pc-windows-msvc.exe",
[SupportedArch.LinuxAarch64]: "clash-aarch64-unknown-linux-gnu-static-crt",
[SupportedArch.LinuxAmd64]: "clash-x86_64-unknown-linux-gnu-static-crt",
[SupportedArch.LinuxI386]: "clash-i686-unknown-linux-gnu-static-crt",
[SupportedArch.DarwinArm64]: "clash-aarch64-apple-darwin",
[SupportedArch.DarwinX64]: "clash-x86_64-apple-darwin",
[SupportedArch.LinuxArmv7]: "clash-armv7-unknown-linux-gnueabi-static-crt",
[SupportedArch.LinuxArmv7hf]: "clash-armv7-unknown-linux-gnueabihf",
} satisfies ArchMapping;
return {
name: "clash_rs_alpha",
version: alphaVersion,
archMapping,
};
};
export const resolveClashPremium = async (): LatestVersionResolver => {
const latestRelease = await octokit.rest.repos.getLatestRelease(
applyProxy({
@@ -115,7 +146,7 @@ export const resolveClashPremium = async (): LatestVersionResolver => {
const archMapping: ArchMapping = {
[SupportedArch.WindowsX86_32]: "clash-windows-386-n{}.zip",
[SupportedArch.WindowsX86_64]: "clash-windows-amd64-n{}.zip",
// [SupportedArch.WindowsAarch64]: "clash-windows-arm64-n{}.zip",
[SupportedArch.WindowsArm64]: "clash-windows-arm64-n{}.zip",
[SupportedArch.LinuxAarch64]: "clash-linux-arm64-n{}.gz",
[SupportedArch.LinuxAmd64]: "clash-linux-amd64-n{}.gz",
[SupportedArch.LinuxI386]: "clash-linux-386-n{}.gz",

View File

@@ -11,6 +11,7 @@ import {
getClashBackupInfo,
getClashMetaAlphaInfo,
getClashMetaInfo,
getClashRustAlphaInfo,
getClashRustInfo,
getNyanpasuServiceInfo,
} from "./resource";
@@ -197,4 +198,8 @@ export class Resolve {
public async clashRust() {
return await this.sidecar(getClashRustInfo(this.infoOption));
}
public async clashRustAlpha() {
return await this.sidecar(getClashRustAlphaInfo(this.infoOption));
}
}

View File

@@ -6,7 +6,10 @@ import {
CLASH_META_MANIFEST,
} from "../manifest/clash-meta";
import { CLASH_MANIFEST } from "../manifest/clash-premium";
import { CLASH_RS_MANIFEST } from "../manifest/clash-rs";
import {
CLASH_RS_ALPHA_MANIFEST,
CLASH_RS_MANIFEST,
} from "../manifest/clash-rs";
import { BinInfo, SupportedArch } from "../types";
import { getProxyAgent } from "./";
import { SIDECAR_HOST } from "./consts";
@@ -26,6 +29,8 @@ function mappingArch(platform: NodeJS.Platform, arch: NodeArch): SupportedArch {
return SupportedArch.WindowsX86_64;
case "win32-ia32":
return SupportedArch.WindowsX86_32;
case "win32-arm64":
return SupportedArch.WindowsArm64;
case "linux-x64":
return SupportedArch.LinuxAmd64;
case "linux-ia32":
@@ -206,6 +211,68 @@ export const getClashRustInfo = ({
};
};
export const getClashRsAlphaLatestVersion = async () => {
const { VERSION_URL } = CLASH_RS_ALPHA_MANIFEST;
try {
const opts = {} as Partial<RequestInit>;
const httpProxy = getProxyAgent();
if (httpProxy) {
opts.agent = httpProxy;
}
const response = await fetch(VERSION_URL!, {
method: "GET",
...opts,
});
const v = (await response.text()).trim().split(" ").pop()!;
consola.info(`Clash Rs Alpha latest release version: ${v}`);
return v.trim();
} catch (error) {
console.error("Error fetching latest release version:", error);
process.exit(1);
}
};
export const getClashRustAlphaInfo = async ({
platform,
arch,
sidecarHost,
}: {
platform: string;
arch: string;
sidecarHost?: string;
}): Promise<BinInfo> => {
const { ARCH_MAPPING, URL_PREFIX } = CLASH_RS_ALPHA_MANIFEST;
const version = await getClashRsAlphaLatestVersion();
const archLabel = mappingArch(platform as NodeJS.Platform, arch as NodeArch);
const name = ARCH_MAPPING[archLabel].replace("{}", version as string);
const isWin = platform === "win32";
const exeFile = `${name}`;
const downloadURL = `${URL_PREFIX}/${name}`;
const tmpFile = `${name}`;
const targetFile = `clash-rs-alpha-${sidecarHost}${isWin ? ".exe" : ""}`;
return {
name: "clash-rs-alpha",
targetFile,
exeFile,
tmpFile,
downloadURL,
};
};
export const getMetaAlphaLatestVersion = async () => {
const { VERSION_URL } = CLASH_META_ALPHA_MANIFEST;

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 7320;
private const uint CodeGenVersion = 7331;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@@ -138,6 +138,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// Ensure that conditions met for that branch are also met for the current one.
// Prefer the latest sources for the phi node.
int undefCount = 0;
for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
{
BasicBlock phiBlock = phiNode.GetBlock(i);
@@ -159,6 +161,26 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return match;
}
}
else if (phiSource.Type == OperandType.Undefined)
{
undefCount++;
}
}
// If all sources but one are undefined, we can assume that the one
// that is not undefined is the right one.
if (undefCount == phiNode.SourcesCount - 1)
{
for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
{
Operand phiSource = phiNode.GetSource(i);
if (phiSource.Type != OperandType.Undefined)
{
return phiSource;
}
}
}
}

View File

@@ -61,7 +61,7 @@ pub enum DnsClient {
stream: ProxyClientStream<MonProxyStream<ShadowTcpStream>>,
},
UdpRemote {
socket: MonProxySocket,
socket: MonProxySocket<ShadowUdpSocket>,
ns: Address,
control: UdpSocketControlData,
server_windows: LruCache<u64, PacketWindowFilter>,

View File

@@ -217,7 +217,7 @@ where
peer_addr: SocketAddr,
bypassed_ipv4_socket: Option<ShadowUdpSocket>,
bypassed_ipv6_socket: Option<ShadowUdpSocket>,
proxied_socket: Option<MonProxySocket>,
proxied_socket: Option<MonProxySocket<ShadowUdpSocket>>,
keepalive_tx: mpsc::Sender<SocketAddr>,
keepalive_flag: bool,
balancer: PingBalancer,
@@ -409,7 +409,7 @@ where
#[inline]
async fn receive_from_proxied_opt(
socket: &Option<MonProxySocket>,
socket: &Option<MonProxySocket<ShadowUdpSocket>>,
buf: &mut Vec<u8>,
) -> io::Result<(usize, Address, Option<UdpSocketControlData>)> {
match *socket {

View File

@@ -3,25 +3,44 @@
use std::{io, net::SocketAddr, sync::Arc};
use shadowsocks::{
relay::{socks5::Address, udprelay::options::UdpSocketControlData},
relay::{
socks5::Address,
udprelay::{options::UdpSocketControlData, DatagramReceive, DatagramSend},
},
ProxySocket,
};
use tokio::net::ToSocketAddrs;
use super::flow::FlowStat;
/// Monitored `ProxySocket`
pub struct MonProxySocket {
socket: ProxySocket,
pub struct MonProxySocket<S> {
socket: ProxySocket<S>,
flow_stat: Arc<FlowStat>,
}
impl MonProxySocket {
impl<S> MonProxySocket<S> {
/// Create a new socket with flow monitor
pub fn from_socket(socket: ProxySocket, flow_stat: Arc<FlowStat>) -> MonProxySocket {
pub fn from_socket(socket: ProxySocket<S>, flow_stat: Arc<FlowStat>) -> MonProxySocket<S> {
MonProxySocket { socket, flow_stat }
}
/// Get the underlying `ProxySocket<S>` immutable reference
#[inline]
pub fn get_ref(&self) -> &ProxySocket<S> {
&self.socket
}
/// Get the flow statistic data
#[inline]
pub fn flow_stat(&self) -> &FlowStat {
&self.flow_stat
}
}
impl<S> MonProxySocket<S>
where
S: DatagramSend,
{
/// Send a UDP packet to addr through proxy
#[inline]
pub async fn send(&self, addr: &Address, payload: &[u8]) -> io::Result<()> {
@@ -47,7 +66,7 @@ impl MonProxySocket {
/// Send a UDP packet to target from proxy
#[inline]
pub async fn send_to<A: ToSocketAddrs>(&self, target: A, addr: &Address, payload: &[u8]) -> io::Result<()> {
pub async fn send_to(&self, target: SocketAddr, addr: &Address, payload: &[u8]) -> io::Result<()> {
let n = self.socket.send_to(target, addr, payload).await?;
self.flow_stat.incr_tx(n as u64);
@@ -56,9 +75,9 @@ impl MonProxySocket {
/// Send a UDP packet to target from proxy
#[inline]
pub async fn send_to_with_ctrl<A: ToSocketAddrs>(
pub async fn send_to_with_ctrl(
&self,
target: A,
target: SocketAddr,
addr: &Address,
control: &UdpSocketControlData,
payload: &[u8],
@@ -68,7 +87,12 @@ impl MonProxySocket {
Ok(())
}
}
impl<S> MonProxySocket<S>
where
S: DatagramReceive,
{
/// Receive packet from Shadowsocks' UDP server
///
/// This function will use `recv_buf` to store intermediate data, so it has to be big enough to store the whole shadowsocks' packet
@@ -126,14 +150,4 @@ impl MonProxySocket {
Ok((n, peer_addr, addr, control))
}
#[inline]
pub fn get_ref(&self) -> &ProxySocket {
&self.socket
}
#[inline]
pub fn flow_stat(&self) -> &FlowStat {
&self.flow_stat
}
}

View File

@@ -17,7 +17,10 @@ use shadowsocks::{
config::ServerUser,
crypto::CipherCategory,
lookup_then,
net::{get_ip_stack_capabilities, AcceptOpts, AddrFamily, UdpSocket as OutboundUdpSocket},
net::{
get_ip_stack_capabilities, AcceptOpts, AddrFamily, UdpSocket as OutboundUdpSocket,
UdpSocket as InboundUdpSocket,
},
relay::{
socks5::Address,
udprelay::{options::UdpSocketControlData, ProxySocket, MAXIMUM_UDP_PAYLOAD_SIZE},
@@ -87,7 +90,7 @@ pub struct UdpServer {
keepalive_tx: mpsc::Sender<NatKey>,
keepalive_rx: mpsc::Receiver<NatKey>,
time_to_live: Duration,
listener: Arc<MonProxySocket>,
listener: Arc<MonProxySocket<InboundUdpSocket>>,
svr_cfg: ServerConfig,
}
@@ -276,7 +279,7 @@ impl UdpServer {
async fn recv_one_packet(
context: &ServiceContext,
l: &MonProxySocket,
l: &MonProxySocket<InboundUdpSocket>,
buffer: &mut [u8],
) -> Option<(usize, SocketAddr, Address, Option<UdpSocketControlData>)> {
let (n, peer_addr, target_addr, control) = match l.recv_from_with_ctrl(buffer).await {
@@ -316,7 +319,7 @@ impl UdpServer {
async fn send_packet(
&mut self,
listener: &Arc<MonProxySocket>,
listener: &Arc<MonProxySocket<InboundUdpSocket>>,
peer_addr: SocketAddr,
target_addr: Address,
control: Option<UdpSocketControlData>,
@@ -394,7 +397,7 @@ impl Drop for UdpAssociation {
impl UdpAssociation {
fn new_association(
context: Arc<ServiceContext>,
inbound: Arc<MonProxySocket>,
inbound: Arc<MonProxySocket<InboundUdpSocket>>,
peer_addr: SocketAddr,
keepalive_tx: mpsc::Sender<NatKey>,
) -> UdpAssociation {
@@ -405,7 +408,7 @@ impl UdpAssociation {
#[cfg(feature = "aead-cipher-2022")]
fn new_session(
context: Arc<ServiceContext>,
inbound: Arc<MonProxySocket>,
inbound: Arc<MonProxySocket<InboundUdpSocket>>,
peer_addr: SocketAddr,
keepalive_tx: mpsc::Sender<NatKey>,
client_session_id: u64,
@@ -447,7 +450,7 @@ struct UdpAssociationContext {
outbound_ipv6_socket: Option<OutboundUdpSocket>,
keepalive_tx: mpsc::Sender<NatKey>,
keepalive_flag: bool,
inbound: Arc<MonProxySocket>,
inbound: Arc<MonProxySocket<InboundUdpSocket>>,
// AEAD 2022
client_session: Option<ClientSessionContext>,
server_session_id: u64,
@@ -472,7 +475,7 @@ fn generate_server_session_id() -> u64 {
impl UdpAssociationContext {
fn create(
context: Arc<ServiceContext>,
inbound: Arc<MonProxySocket>,
inbound: Arc<MonProxySocket<InboundUdpSocket>>,
peer_addr: SocketAddr,
keepalive_tx: mpsc::Sender<NatKey>,
client_session_id: Option<u64>,

View File

@@ -24,7 +24,7 @@ use std::{
))]
use futures::future;
use futures::ready;
use pin_project::pin_project;
#[cfg(any(
target_os = "linux",
target_os = "android",
@@ -86,9 +86,7 @@ fn make_mtu_error(packet_size: usize, mtu: usize) -> io::Error {
/// Wrappers for outbound `UdpSocket`
#[derive(Debug)]
#[pin_project]
pub struct UdpSocket {
#[pin]
socket: tokio::net::UdpSocket,
mtu: Option<usize>,
}

View File

@@ -0,0 +1,209 @@
use std::{
future::Future,
io,
net::SocketAddr,
ops::Deref,
pin::Pin,
task::{Context, Poll},
};
use futures::ready;
use pin_project::pin_project;
use tokio::io::ReadBuf;
use crate::net::UdpSocket;
/// A socket I/O object that can transport datagram
pub trait DatagramSocket {
/// Local binded address
fn local_addr(&self) -> io::Result<SocketAddr>;
}
/// A socket I/O object that can receive datagram
pub trait DatagramReceive {
/// `recv` data into `buf`
fn poll_recv(&self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>>;
/// `recv` data into `buf` with source address
fn poll_recv_from(&self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<SocketAddr>>;
/// Check if the underlying I/O object is ready for `recv`
fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>>;
}
/// A socket I/O object that can send datagram
pub trait DatagramSend {
/// `send` data with `buf`, returning the sent bytes
fn poll_send(&self, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>>;
/// `send` data with `buf` to `target`, returning the sent bytes
fn poll_send_to(&self, cx: &mut Context<'_>, buf: &[u8], target: SocketAddr) -> Poll<io::Result<usize>>;
/// Check if the underlying I/O object is ready for `send`
fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>>;
}
impl DatagramSocket for UdpSocket {
fn local_addr(&self) -> io::Result<SocketAddr> {
self.deref().local_addr()
}
}
impl DatagramReceive for UdpSocket {
fn poll_recv(&self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
UdpSocket::poll_recv(self, cx, buf)
}
fn poll_recv_from(&self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<SocketAddr>> {
UdpSocket::poll_recv_from(self, cx, buf)
}
fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.deref().poll_recv_ready(cx)
}
}
impl DatagramSend for UdpSocket {
fn poll_send(&self, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>> {
UdpSocket::poll_send(self, cx, buf)
}
fn poll_send_to(&self, cx: &mut Context<'_>, buf: &[u8], target: SocketAddr) -> Poll<io::Result<usize>> {
UdpSocket::poll_send_to(self, cx, buf, target)
}
fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.deref().poll_send_ready(cx)
}
}
/// Future for `recv`
#[pin_project]
pub struct RecvFut<'a, S: DatagramReceive + ?Sized> {
#[pin]
io: &'a S,
buf: &'a mut [u8],
}
impl<'a, S: DatagramReceive + ?Sized> Future for RecvFut<'a, S> {
type Output = io::Result<usize>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let mut read_buf = ReadBuf::new(this.buf);
ready!(this.io.poll_recv(cx, &mut read_buf))?;
Ok(read_buf.filled().len()).into()
}
}
/// Future for `recv_from`
#[pin_project]
pub struct RecvFromFut<'a, S: DatagramReceive + ?Sized> {
#[pin]
io: &'a S,
buf: &'a mut [u8],
}
impl<'a, S: DatagramReceive + ?Sized> Future for RecvFromFut<'a, S> {
type Output = io::Result<(usize, SocketAddr)>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let mut read_buf = ReadBuf::new(this.buf);
let src_addr = ready!(this.io.poll_recv_from(cx, &mut read_buf))?;
Ok((read_buf.filled().len(), src_addr)).into()
}
}
/// Future for `recv_ready`
pub struct RecvReadyFut<'a, S: DatagramReceive + ?Sized> {
io: &'a S,
}
impl<'a, S: DatagramReceive + ?Sized> Future for RecvReadyFut<'a, S> {
type Output = io::Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.io.poll_recv_ready(cx)
}
}
/// Future for `send`
pub struct SendFut<'a, S: DatagramSend + ?Sized> {
io: &'a S,
buf: &'a [u8],
}
impl<'a, S: DatagramSend + ?Sized> Future for SendFut<'a, S> {
type Output = io::Result<usize>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.io.poll_send(cx, self.buf)
}
}
/// Future for `send_to`
pub struct SendToFut<'a, S: DatagramSend + ?Sized> {
io: &'a S,
target: SocketAddr,
buf: &'a [u8],
}
impl<'a, S: DatagramSend + ?Sized> Future for SendToFut<'a, S> {
type Output = io::Result<usize>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.io.poll_send_to(cx, self.buf, self.target)
}
}
/// Future for `recv_ready`
pub struct SendReadyFut<'a, S: DatagramSend + ?Sized> {
io: &'a S,
}
impl<'a, S: DatagramSend + ?Sized> Future for SendReadyFut<'a, S> {
type Output = io::Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.io.poll_send_ready(cx)
}
}
/// Extension methods for `DatagramReceive`
pub trait DatagramReceiveExt: DatagramReceive {
/// Async method for `poll_recv`
fn recv<'a, 'b>(&'a self, buf: &'a mut [u8]) -> RecvFut<'a, Self> {
RecvFut { io: self, buf }
}
/// Async method for `poll_recv_from`
fn recv_from<'a, 'b>(&'a self, buf: &'a mut [u8]) -> RecvFromFut<'a, Self> {
RecvFromFut { io: self, buf }
}
/// Async method for `poll_recv_ready`
fn recv_ready<'a>(&'a self) -> RecvReadyFut<'a, Self> {
RecvReadyFut { io: self }
}
}
impl<S: DatagramReceive> DatagramReceiveExt for S {}
/// Extension methods for `DatagramSend`
pub trait DatagramSendExt: DatagramSend {
/// Async method for `poll_send`
fn send<'a>(&'a self, buf: &'a [u8]) -> SendFut<'a, Self> {
SendFut { io: self, buf }
}
/// Async method for `poll_send_to`
fn send_to<'a>(&'a self, buf: &'a [u8], target: SocketAddr) -> SendToFut<'a, Self> {
SendToFut { io: self, target, buf }
}
/// Async method for `poll_send_ready`
fn send_ready<'a>(&'a self) -> SendReadyFut<'a, Self> {
SendReadyFut { io: self }
}
}
impl<S: DatagramSend> DatagramSendExt for S {}

View File

@@ -50,10 +50,12 @@
use std::time::Duration;
pub use self::proxy_socket::ProxySocket;
pub use compat::{DatagramReceive, DatagramReceiveExt, DatagramSend, DatagramSendExt, DatagramSocket};
mod aead;
#[cfg(feature = "aead-cipher-2022")]
mod aead_2022;
mod compat;
pub mod crypto_io;
pub mod options;
pub mod proxy_socket;

View File

@@ -12,7 +12,7 @@ use byte_string::ByteStr;
use bytes::{Bytes, BytesMut};
use log::{info, trace, warn};
use once_cell::sync::Lazy;
use tokio::{io::ReadBuf, net::ToSocketAddrs, time};
use tokio::{io::ReadBuf, time};
use crate::{
config::{ServerAddr, ServerConfig, ServerUserManager},
@@ -22,9 +22,12 @@ use crate::{
relay::{socks5::Address, udprelay::options::UdpSocketControlData},
};
use super::crypto_io::{
decrypt_client_payload, decrypt_server_payload, encrypt_client_payload, encrypt_server_payload, ProtocolError,
ProtocolResult,
use super::{
compat::{DatagramReceive, DatagramReceiveExt, DatagramSend, DatagramSendExt, DatagramSocket},
crypto_io::{
decrypt_client_payload, decrypt_server_payload, encrypt_client_payload, encrypt_server_payload, ProtocolError,
ProtocolResult,
},
};
#[cfg(unix)]
@@ -70,9 +73,9 @@ pub type ProxySocketResult<T> = Result<T, ProxySocketError>;
/// UDP client for communicating with ShadowSocks' server
#[derive(Debug)]
pub struct ProxySocket {
pub struct ProxySocket<S> {
socket_type: UdpSocketType,
socket: ShadowUdpSocket,
io: S,
method: CipherKind,
key: Box<[u8]>,
send_timeout: Option<Duration>,
@@ -82,9 +85,12 @@ pub struct ProxySocket {
user_manager: Option<Arc<ServerUserManager>>,
}
impl ProxySocket {
impl ProxySocket<ShadowUdpSocket> {
/// Create a client to communicate with Shadowsocks' UDP server (outbound)
pub async fn connect(context: SharedContext, svr_cfg: &ServerConfig) -> ProxySocketResult<ProxySocket> {
pub async fn connect(
context: SharedContext,
svr_cfg: &ServerConfig,
) -> ProxySocketResult<ProxySocket<ShadowUdpSocket>> {
ProxySocket::connect_with_opts(context, svr_cfg, &DEFAULT_CONNECT_OPTS)
.await
.map_err(Into::into)
@@ -95,7 +101,7 @@ impl ProxySocket {
context: SharedContext,
svr_cfg: &ServerConfig,
opts: &ConnectOpts,
) -> ProxySocketResult<ProxySocket> {
) -> ProxySocketResult<ProxySocket<ShadowUdpSocket>> {
// Note: Plugins doesn't support UDP relay
let socket = ShadowUdpSocket::connect_server_with_opts(&context, svr_cfg.udp_external_addr(), opts).await?;
@@ -115,42 +121,11 @@ impl ProxySocket {
))
}
/// Create a `ProxySocket` from a `UdpSocket`
pub fn from_socket<S>(
socket_type: UdpSocketType,
/// Create a `ProxySocket` binding to a specific address (inbound)
pub async fn bind(
context: SharedContext,
svr_cfg: &ServerConfig,
socket: S,
) -> ProxySocket
where
S: Into<ShadowUdpSocket>,
{
let key = svr_cfg.key().to_vec().into_boxed_slice();
let method = svr_cfg.method();
// NOTE: svr_cfg.timeout() is not for this socket, but for associations.
ProxySocket {
socket_type,
socket: socket.into(),
method,
key,
send_timeout: None,
recv_timeout: None,
context,
identity_keys: match socket_type {
UdpSocketType::Client => svr_cfg.clone_identity_keys(),
UdpSocketType::Server => Arc::new(Vec::new()),
},
user_manager: match socket_type {
UdpSocketType::Client => None,
UdpSocketType::Server => svr_cfg.clone_user_manager(),
},
}
}
/// Create a `ProxySocket` binding to a specific address (inbound)
pub async fn bind(context: SharedContext, svr_cfg: &ServerConfig) -> ProxySocketResult<ProxySocket> {
) -> ProxySocketResult<ProxySocket<ShadowUdpSocket>> {
ProxySocket::bind_with_opts(context, svr_cfg, AcceptOpts::default())
.await
.map_err(Into::into)
@@ -161,7 +136,7 @@ impl ProxySocket {
context: SharedContext,
svr_cfg: &ServerConfig,
opts: AcceptOpts,
) -> ProxySocketResult<ProxySocket> {
) -> ProxySocketResult<ProxySocket<ShadowUdpSocket>> {
// Plugins doesn't support UDP
let socket = match svr_cfg.udp_external_addr() {
ServerAddr::SocketAddr(sa) => ShadowUdpSocket::listen_with_opts(sa, opts).await?,
@@ -179,7 +154,54 @@ impl ProxySocket {
socket,
))
}
}
impl<S> ProxySocket<S> {
/// Create a `ProxySocket` from a I/O object that impls `DatagramTransport`
pub fn from_socket(
socket_type: UdpSocketType,
context: SharedContext,
svr_cfg: &ServerConfig,
socket: S,
) -> ProxySocket<S> {
let key = svr_cfg.key().to_vec().into_boxed_slice();
let method = svr_cfg.method();
// NOTE: svr_cfg.timeout() is not for this socket, but for associations.
ProxySocket {
socket_type,
io: socket,
method,
key,
send_timeout: None,
recv_timeout: None,
context,
identity_keys: match socket_type {
UdpSocketType::Client => svr_cfg.clone_identity_keys(),
UdpSocketType::Server => Arc::new(Vec::new()),
},
user_manager: match socket_type {
UdpSocketType::Client => None,
UdpSocketType::Server => svr_cfg.clone_user_manager(),
},
}
}
/// Set `send` timeout, `None` will clear timeout
pub fn set_send_timeout(&mut self, t: Option<Duration>) {
self.send_timeout = t;
}
/// Set `recv` timeout, `None` will clear timeout
pub fn set_recv_timeout(&mut self, t: Option<Duration>) {
self.recv_timeout = t;
}
}
impl<S> ProxySocket<S>
where
S: DatagramSend,
{
fn encrypt_send_buffer(
&self,
addr: &Address,
@@ -241,8 +263,8 @@ impl ProxySocket {
);
let send_len = match self.send_timeout {
None => self.socket.send(&send_buf).await?,
Some(d) => match time::timeout(d, self.socket.send(&send_buf)).await {
None => self.io.send(&send_buf).await?,
Some(d) => match time::timeout(d, self.io.send(&send_buf)).await {
Ok(Ok(l)) => l,
Ok(Err(err)) => return Err(err.into()),
Err(..) => return Err(io::Error::from(ErrorKind::TimedOut).into()),
@@ -295,7 +317,7 @@ impl ProxySocket {
let n_send_buf = send_buf.len();
match self.socket.poll_send(cx, &send_buf).map_err(|x| x.into()) {
match self.io.poll_send(cx, &send_buf).map_err(|x| x.into()) {
Poll::Ready(Ok(l)) => {
if l == n_send_buf {
Poll::Ready(Ok(payload.len()))
@@ -340,14 +362,14 @@ impl ProxySocket {
self.encrypt_send_buffer(addr, control, &self.identity_keys, payload, &mut send_buf)?;
info!(
"UDP server client send to {}, payload length {} bytes, packet length {} bytes",
"UDP server client poll_send_to to {}, payload length {} bytes, packet length {} bytes",
target,
payload.len(),
send_buf.len()
);
let n_send_buf = send_buf.len();
match self.socket.poll_send_to(cx, &send_buf, target).map_err(|x| x.into()) {
match self.io.poll_send_to(cx, &send_buf, target).map_err(|x| x.into()) {
Poll::Ready(Ok(l)) => {
if l == n_send_buf {
Poll::Ready(Ok(payload.len()))
@@ -363,25 +385,20 @@ impl ProxySocket {
///
/// Check if socket is ready to `send`, or writable.
pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<ProxySocketResult<()>> {
self.socket.poll_send_ready(cx).map_err(|x| x.into())
self.io.poll_send_ready(cx).map_err(|x| x.into())
}
/// Send a UDP packet to target through proxy `target`
pub async fn send_to<A: ToSocketAddrs>(
&self,
target: A,
addr: &Address,
payload: &[u8],
) -> ProxySocketResult<usize> {
pub async fn send_to(&self, target: SocketAddr, addr: &Address, payload: &[u8]) -> ProxySocketResult<usize> {
self.send_to_with_ctrl(target, addr, &DEFAULT_SOCKET_CONTROL, payload)
.await
.map_err(Into::into)
}
/// Send a UDP packet to target through proxy `target`
pub async fn send_to_with_ctrl<A: ToSocketAddrs>(
pub async fn send_to_with_ctrl(
&self,
target: A,
target: SocketAddr,
addr: &Address,
control: &UdpSocketControlData,
payload: &[u8],
@@ -390,7 +407,7 @@ impl ProxySocket {
self.encrypt_send_buffer(addr, control, &self.identity_keys, payload, &mut send_buf)?;
trace!(
"UDP server client send to, addr {}, control: {:?}, payload length {} bytes, packet length {} bytes",
"UDP server client send_to to, addr {}, control: {:?}, payload length {} bytes, packet length {} bytes",
addr,
control,
payload.len(),
@@ -398,8 +415,8 @@ impl ProxySocket {
);
let send_len = match self.send_timeout {
None => self.socket.send_to(&send_buf, target).await?,
Some(d) => match time::timeout(d, self.socket.send_to(&send_buf, target)).await {
None => self.io.send_to(&send_buf, target).await?,
Some(d) => match time::timeout(d, self.io.send_to(&send_buf, target)).await {
Ok(Ok(l)) => l,
Ok(Err(err)) => return Err(err.into()),
Err(..) => return Err(io::Error::from(ErrorKind::TimedOut).into()),
@@ -408,7 +425,7 @@ impl ProxySocket {
if send_buf.len() != send_len {
warn!(
"UDP server client send {} bytes, but actually sent {} bytes",
"UDP server client send_to {} bytes, but actually sent {} bytes",
send_buf.len(),
send_len
);
@@ -416,7 +433,12 @@ impl ProxySocket {
Ok(send_len)
}
}
impl<S> ProxySocket<S>
where
S: DatagramReceive,
{
fn decrypt_recv_buffer(
&self,
recv_buf: &mut [u8],
@@ -448,10 +470,9 @@ impl ProxySocket {
&self,
recv_buf: &mut [u8],
) -> ProxySocketResult<(usize, Address, usize, Option<UdpSocketControlData>)> {
// Waiting for response from server SERVER -> CLIENT
let recv_n = match self.recv_timeout {
None => self.socket.recv(recv_buf).await?,
Some(d) => match time::timeout(d, self.socket.recv(recv_buf)).await {
None => self.io.recv(recv_buf).await?,
Some(d) => match time::timeout(d, self.io.recv(recv_buf)).await {
Ok(Ok(l)) => l,
Ok(Err(err)) => return Err(err.into()),
Err(..) => return Err(io::Error::from(ErrorKind::TimedOut).into()),
@@ -498,8 +519,8 @@ impl ProxySocket {
) -> ProxySocketResult<(usize, SocketAddr, Address, usize, Option<UdpSocketControlData>)> {
// Waiting for response from server SERVER -> CLIENT
let (recv_n, target_addr) = match self.recv_timeout {
None => self.socket.recv_from(recv_buf).await?,
Some(d) => match time::timeout(d, self.socket.recv_from(recv_buf)).await {
None => self.io.recv_from(recv_buf).await?,
Some(d) => match time::timeout(d, self.io.recv_from(recv_buf)).await {
Ok(Ok(l)) => l,
Ok(Err(err)) => return Err(err.into()),
Err(..) => return Err(io::Error::from(ErrorKind::TimedOut).into()),
@@ -542,7 +563,7 @@ impl ProxySocket {
cx: &mut Context<'_>,
recv_buf: &mut ReadBuf,
) -> Poll<ProxySocketResult<(usize, Address, usize, Option<UdpSocketControlData>)>> {
ready!(self.socket.poll_recv(cx, recv_buf))?;
ready!(self.io.poll_recv(cx, recv_buf))?;
let n_recv = recv_buf.filled().len();
@@ -570,7 +591,7 @@ impl ProxySocket {
cx: &mut Context<'_>,
recv_buf: &mut ReadBuf,
) -> Poll<ProxySocketResult<(usize, SocketAddr, Address, usize, Option<UdpSocketControlData>)>> {
let src = ready!(self.socket.poll_recv_from(cx, recv_buf))?;
let src = ready!(self.io.poll_recv_from(cx, recv_buf))?;
let n_recv = recv_buf.filled().len();
match self.decrypt_recv_buffer(recv_buf.filled_mut(), self.user_manager.as_deref()) {
@@ -581,29 +602,27 @@ impl ProxySocket {
/// poll family functions
pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<ProxySocketResult<()>> {
self.socket.poll_recv_ready(cx).map_err(|x| x.into())
self.io.poll_recv_ready(cx).map_err(|x| x.into())
}
}
impl<S> ProxySocket<S>
where
S: DatagramSocket,
{
/// Get local addr of socket
pub fn local_addr(&self) -> io::Result<SocketAddr> {
self.socket.local_addr()
}
/// Set `send` timeout, `None` will clear timeout
pub fn set_send_timeout(&mut self, t: Option<Duration>) {
self.send_timeout = t;
}
/// Set `recv` timeout, `None` will clear timeout
pub fn set_recv_timeout(&mut self, t: Option<Duration>) {
self.recv_timeout = t;
self.io.local_addr()
}
}
#[cfg(unix)]
impl AsRawFd for ProxySocket {
impl<S> AsRawFd for ProxySocket<S>
where
S: AsRawFd,
{
/// Retrieve raw fd of the outbound socket
fn as_raw_fd(&self) -> RawFd {
self.socket.as_raw_fd()
self.io.as_raw_fd()
}
}

View File

@@ -8,6 +8,7 @@ use shadowsocks::{
config::{ServerConfig, ServerType},
context::{Context, SharedContext},
crypto::CipherKind,
net::UdpSocket as ShadowUdpSocket,
relay::{socks5::Address, udprelay::ProxySocket},
};
@@ -15,7 +16,7 @@ async fn handle_udp_server_client(
peer_addr: SocketAddr,
remote_addr: Address,
payload: &[u8],
socket: &ProxySocket,
socket: &ProxySocket<ShadowUdpSocket>,
) -> io::Result<()> {
let remote_socket = UdpSocket::bind("0.0.0.0:0").await?;

View File

@@ -145,7 +145,6 @@ build_macos_dmg:
--hide-extension "SFM.app" \
--app-drop-link 0 0 \
--skip-jenkins \
--codesign "B2324162A090F01F96111CF802A83F0F36674F80" \
--notarize "notarytool-password" \
"../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app"

View File

@@ -114,7 +114,7 @@ dependencies {
implementation "com.blacksquircle.ui:editorkit:2.2.0"
implementation "com.blacksquircle.ui:language-json:2.2.0"
implementation("com.android.tools.smali:smali-dexlib2:3.0.7") {
implementation("com.android.tools.smali:smali-dexlib2:3.0.8") {
exclude group: "com.google.guava", module: "guava"
}
implementation "com.google.guava:guava:33.0.0-android"

View File

@@ -5,8 +5,8 @@ buildscript {
}
plugins {
id 'com.android.application' version '8.6.0' apply false
id 'com.android.library' version '8.6.0' apply false
id 'com.android.application' version '8.6.1' apply false
id 'com.android.library' version '8.6.1' apply false
id 'org.jetbrains.kotlin.android' version '1.9.23' apply false
id 'com.google.devtools.ksp' version '1.9.23-1.0.20' apply false
id 'com.github.triplet.play' version '3.8.4' apply false

View File

@@ -1,3 +1,3 @@
VERSION_CODE=392
VERSION_NAME=1.9.5
VERSION_CODE=394
VERSION_NAME=1.9.6
GO_VERSION=go1.23.1

View File

@@ -1,40 +1,39 @@
import Foundation
import GRDB
actor Database {
private static var writer: (any DatabaseWriter)?
enum Database {
static let sharedWriter = makeShared()
static func sharedWriter() throws -> any DatabaseWriter {
if let writer {
return writer
}
try FileManager.default.createDirectory(at: FilePath.sharedDirectory, withIntermediateDirectories: true)
let database = try DatabasePool(path: FilePath.sharedDirectory.appendingPathComponent("settings.db").relativePath)
var migrator = DatabaseMigrator().disablingDeferredForeignKeyChecks()
migrator.registerMigration("initialize") { db in
try db.create(table: "profiles") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull()
t.column("order", .integer).notNull()
t.column("type", .integer).notNull().defaults(to: ProfileType.local.rawValue)
t.column("path", .text).notNull()
t.column("remoteURL", .text)
t.column("autoUpdate", .boolean).notNull().defaults(to: false)
t.column("lastUpdated", .datetime)
private static func makeShared() -> any DatabaseWriter {
do {
try FileManager.default.createDirectory(at: FilePath.sharedDirectory, withIntermediateDirectories: true)
let database = try DatabasePool(path: FilePath.sharedDirectory.appendingPathComponent("settings.db").relativePath)
var migrator = DatabaseMigrator().disablingDeferredForeignKeyChecks()
migrator.registerMigration("initialize") { db in
try db.create(table: "profiles") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull()
t.column("order", .integer).notNull()
t.column("type", .integer).notNull().defaults(to: ProfileType.local.rawValue)
t.column("path", .text).notNull()
t.column("remoteURL", .text)
t.column("autoUpdate", .boolean).notNull().defaults(to: false)
t.column("lastUpdated", .datetime)
}
try db.create(table: "preferences") { t in
t.primaryKey("name", .text, onConflict: .replace).notNull()
t.column("data", .blob)
}
}
try db.create(table: "preferences") { t in
t.primaryKey("name", .text, onConflict: .replace).notNull()
t.column("data", .blob)
migrator.registerMigration("add_auto_update_interval") { db in
try db.alter(table: "profiles") { t in
t.add(column: "autoUpdateInterval", .integer).notNull().defaults(to: 0)
}
}
try migrator.migrate(database)
return database
} catch {
fatalError(error.localizedDescription)
}
migrator.registerMigration("add_auto_update_interval") { db in
try db.alter(table: "profiles") { t in
t.add(column: "autoUpdateInterval", .integer).notNull().defaults(to: 0)
}
}
try migrator.migrate(database)
writer = database
return database
}
}

View File

@@ -102,5 +102,5 @@ public struct TypedProfile: Transferable, Codable {
}
public extension UTType {
static var profile: UTType { .init(exportedAs: "io.nekohasekai.sfa.profile") }
static var profile: UTType { .init(exportedAs: "io.nekohasekai.sfavt.profile") }
}

View File

@@ -4,37 +4,37 @@ import GRDB
public enum ProfileManager {
public nonisolated static func create(_ profile: Profile) async throws {
profile.order = try await nextOrder()
try await Database.sharedWriter().write { db in
try await Database.sharedWriter.write { db in
try profile.insert(db, onConflict: .fail)
}
}
public nonisolated static func get(_ profileID: Int64) async throws -> Profile? {
try await Database.sharedWriter().read { db in
try await Database.sharedWriter.read { db in
try Profile.fetchOne(db, id: profileID)
}
}
public nonisolated static func get(by profileName: String) async throws -> Profile? {
try await Database.sharedWriter().read { db in
try await Database.sharedWriter.read { db in
try Profile.filter(Column("name") == profileName).fetchOne(db)
}
}
public nonisolated static func delete(_ profile: Profile) async throws {
_ = try await Database.sharedWriter().write { db in
_ = try await Database.sharedWriter.write { db in
try profile.delete(db)
}
}
public nonisolated static func delete(by id: Int64) async throws {
_ = try await Database.sharedWriter().write { db in
_ = try await Database.sharedWriter.write { db in
try Profile.deleteOne(db, id: id)
}
}
public nonisolated static func delete(_ profileList: [Profile]) async throws -> Int {
try await Database.sharedWriter().write { db in
try await Database.sharedWriter.write { db in
try Profile.deleteAll(db, keys: profileList.map {
["id": $0.id!]
})
@@ -42,20 +42,20 @@ public enum ProfileManager {
}
public nonisolated static func delete(by id: [Int64]) async throws -> Int {
try await Database.sharedWriter().write { db in
try await Database.sharedWriter.write { db in
try Profile.deleteAll(db, ids: id)
}
}
public nonisolated static func update(_ profile: Profile) async throws {
_ = try await Database.sharedWriter().write { db in
_ = try await Database.sharedWriter.write { db in
try profile.updateChanges(db)
}
}
public nonisolated static func update(_ profileList: [Profile]) async throws {
// TODO: batch update
try await Database.sharedWriter().write { db in
try await Database.sharedWriter.write { db in
for profile in profileList {
try profile.updateChanges(db)
}
@@ -63,25 +63,25 @@ public enum ProfileManager {
}
public nonisolated static func list() async throws -> [Profile] {
try await Database.sharedWriter().read { db in
try await Database.sharedWriter.read { db in
try Profile.all().order(Column("order").asc).fetchAll(db)
}
}
public nonisolated static func listRemote() async throws -> [Profile] {
try await Database.sharedWriter().read { db in
try await Database.sharedWriter.read { db in
try Profile.filter(Column("type") == ProfileType.remote.rawValue).order(Column("order").asc).fetchAll(db)
}
}
public nonisolated static func listAutoUpdateEnabled() async throws -> [Profile] {
try await Database.sharedWriter().read { db in
try await Database.sharedWriter.read { db in
try Profile.filter(Column("autoUpdate") == true).order(Column("order").asc).fetchAll(db)
}
}
public nonisolated static func nextID() async throws -> Int64 {
try await Database.sharedWriter().read { db in
try await Database.sharedWriter.read { db in
if let lastProfile = try Profile.select(Column("id")).order(Column("id").desc).fetchOne(db) {
return lastProfile.id! + 1
} else {
@@ -91,7 +91,7 @@ public enum ProfileManager {
}
private nonisolated static func nextOrder() async throws -> UInt32 {
try await Database.sharedWriter().read { db in
try await Database.sharedWriter.read { db in
try UInt32(Profile.fetchCount(db))
}
}

View File

@@ -37,7 +37,7 @@ extension SharedPreferences {
}
private nonisolated static func read<T: Codable>(_ name: String) async throws -> T? {
guard let item = try await (Database.sharedWriter().read { db in
guard let item = try await (Database.sharedWriter.read { db in
try Item.fetchOne(db, id: name)
})
else {
@@ -48,12 +48,12 @@ extension SharedPreferences {
private nonisolated static func write(_ name: String, _ value: (some Codable)?) async throws {
if value == nil {
_ = try await Database.sharedWriter().write { db in
_ = try await Database.sharedWriter.write { db in
try Item.deleteOne(db, id: name)
}
} else {
let data = try BinaryEncoder().encode(value)
try await Database.sharedWriter().write { db in
try await Database.sharedWriter.write { db in
try Item(name: name, data: data).insert(db)
}
}

View File

@@ -80,7 +80,7 @@
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>io.nekohasekai.sfa.profile</string>
<string>io.nekohasekai.sfavt.profile</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
@@ -106,7 +106,7 @@
<string>AppIcon.icns</string>
</array>
<key>UTTypeIdentifier</key>
<string>io.nekohasekai.sfa.profile</string>
<string>io.nekohasekai.sfavt.profile</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>

View File

@@ -15,7 +15,7 @@
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>io.nekohasekai.sfa.profile</string>
<string>io.nekohasekai.sfavt.profile</string>
</array>
</dict>
</array>
@@ -62,7 +62,7 @@
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>io.nekohasekai.sfa.profile</string>
<string>io.nekohasekai.sfavt.profile</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
@@ -88,7 +88,7 @@
<string>AppIcon.icns</string>
</array>
<key>UTTypeIdentifier</key>
<string>io.nekohasekai.sfa.profile</string>
<string>io.nekohasekai.sfavt.profile</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>

View File

@@ -15,7 +15,7 @@
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>io.nekohasekai.sfa.profile</string>
<string>io.nekohasekai.sfavt.profile</string>
</array>
</dict>
</array>
@@ -62,7 +62,7 @@
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>io.nekohasekai.sfa.profile</string>
<string>io.nekohasekai.sfavt.profile</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
@@ -88,7 +88,7 @@
<string>AppIcon.icns</string>
</array>
<key>UTTypeIdentifier</key>
<string>io.nekohasekai.sfa.profile</string>
<string>io.nekohasekai.sfavt.profile</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>

View File

@@ -2011,7 +2011,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.9.5;
MARKETING_VERSION = 1.9.6;
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
PRODUCT_NAME = "sing-box";
SDKROOT = appletvos;
@@ -2046,7 +2046,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.9.5;
MARKETING_VERSION = 1.9.6;
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
PRODUCT_NAME = "sing-box";
SDKROOT = appletvos;
@@ -2288,7 +2288,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.9.5;
MARKETING_VERSION = 1.9.6;
OTHER_CODE_SIGN_FLAGS = "--deep";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
PRODUCT_NAME = "sing-box";
@@ -2330,7 +2330,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.9.5;
MARKETING_VERSION = 1.9.6;
OTHER_CODE_SIGN_FLAGS = "--deep";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
PRODUCT_NAME = "sing-box";
@@ -2353,7 +2353,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 272;
CURRENT_PROJECT_VERSION = 276;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 287TTNZF8L;
ENABLE_HARDENED_RUNTIME = YES;
@@ -2371,7 +2371,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.9.5;
MARKETING_VERSION = 1.9.6;
OTHER_CODE_SIGN_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
PRODUCT_NAME = "sing-box";
@@ -2393,7 +2393,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 272;
CURRENT_PROJECT_VERSION = 276;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 287TTNZF8L;
ENABLE_HARDENED_RUNTIME = YES;
@@ -2411,7 +2411,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.9.5;
MARKETING_VERSION = 1.9.6;
OTHER_CODE_SIGN_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt;
PRODUCT_NAME = "sing-box";
@@ -2541,7 +2541,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.9.4;
MARKETING_VERSION = 1.9.6;
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.system;
PRODUCT_NAME = "$(inherited)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2577,7 +2577,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.9.4;
MARKETING_VERSION = 1.9.6;
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.system;
PRODUCT_NAME = "$(inherited)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2619,7 +2619,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.9.4;
MARKETING_VERSION = 1.9.6;
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.standalone;
PRODUCT_NAME = SFM;
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2660,7 +2660,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.9.4;
MARKETING_VERSION = 1.9.6;
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.standalone;
PRODUCT_NAME = SFM;
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@@ -27,7 +27,7 @@ func main() {
objectsMap := project["objects"].(map[string]any)
projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj")))
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfavt"}, newVersion.VersionString())
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.standalone", "io.nekohasekai.sfa.system"}, newVersion.String())
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfavt.standalone", "io.nekohasekai.sfavt.system"}, newVersion.String())
if updated0 || updated1 {
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
}

View File

@@ -40,21 +40,27 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
}
deadline := time.Now().Add(timeout)
var errors []error
err := conn.SetReadDeadline(deadline)
if err != nil {
return E.Cause(err, "set read deadline")
}
defer conn.SetReadDeadline(time.Time{})
for _, sniffer := range sniffers {
if buffer.IsEmpty() {
err = sniffer(ctx, metadata, io.TeeReader(conn, buffer))
} else {
err = sniffer(ctx, metadata, io.MultiReader(bytes.NewReader(buffer.Bytes()), io.TeeReader(conn, buffer)))
for i := 0; ; i++ {
err := conn.SetReadDeadline(deadline)
if err != nil {
return E.Cause(err, "set read deadline")
}
if err == nil {
return nil
_, err = buffer.ReadOnceFrom(conn)
_ = conn.SetReadDeadline(time.Time{})
if err != nil {
if i > 0 {
break
}
return E.Cause(err, "read payload")
}
errors = nil
for _, sniffer := range sniffers {
err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes()))
if err == nil {
return nil
}
errors = append(errors, err)
}
errors = append(errors, err)
}
return E.Errors(errors...)
}

View File

@@ -2,7 +2,12 @@
icon: material/alert-decagram
---
#### 1.10.0-beta.9
#### 1.10.0-beta.10
* Add `process_path_regex` rule item
* Fixes and improvements
### 1.9.6
* Fixes and improvements

View File

@@ -7,12 +7,6 @@ icon: material/apple
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
platform-specific function implementation, such as TUN transparent proxy implementation.
!!! failure "Unavailable"
Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update.
We are working on getting sing-box apps back on the App Store, which should be completed within a week (SFI on the App Store and others on TestFlight are already available).
## :material-graph: Requirements
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+

View File

@@ -4,7 +4,7 @@ icon: material/horse
# Trojan
Trojan is the most commonly used TLS proxy made in China. It can be used in various combinations,
Torjan is the most commonly used TLS proxy made in China. It can be used in various combinations,
but only the combination of uTLS and multiplexing is recommended.
| Protocol and implementation combination | Specification | Resists passive detection | Resists active probes |

View File

@@ -57,7 +57,7 @@ func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes
inet4Addresses: inet4Addresses,
inet6Addresses: inet6Addresses,
gso: gso,
events: make(chan wgTun.Event),
events: make(chan wgTun.Event, 1),
}, nil
}

View File

@@ -5,9 +5,9 @@ PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git
PKG_SOURCE_DATE:=2024-09-18
PKG_SOURCE_VERSION:=3676d1b79fe8567c9375d3e812f84eb683c8f77d
PKG_MIRROR_HASH:=1f9f436a001213ae8f68f962e34366b901bba5a8926a8a89aaaee7ba2e85e44b
PKG_SOURCE_DATE:=2024-09-19
PKG_SOURCE_VERSION:=a08aa10630813485d7899e65ff59c3fae56f8364
PKG_MIRROR_HASH:=415cafd17a7c3362da952b9c5ecaa8f38d01e79b9609fdbc2384be225ae97451
PKG_LICENSE:=MIT
PKG_MAINTAINER:=Joseph Mory <morytyann@gmail.com>
@@ -16,7 +16,7 @@ PKG_BUILD_DEPENDS:=golang/host
PKG_BUILD_PARALLEL:=1
PKG_BUILD_FLAGS:=no-mips16
PKG_BUILD_VERSION:=alpha-3676d1b
PKG_BUILD_VERSION:=alpha-a08aa10
PKG_BUILD_TIME:=$(shell date -u -Iseconds)
GO_PKG:=github.com/metacubex/mihomo

View File

@@ -21,13 +21,13 @@ define Download/geoip
HASH:=b9d2ae86f70922be6426d734464e395ae00f537af40eb69d74ee41d65b29fdf0
endef
GEOSITE_VER:=20240914091803
GEOSITE_VER:=20240920063125
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
define Download/geosite
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
URL_FILE:=dlc.dat
FILE:=$(GEOSITE_FILE)
HASH:=c171f61d3ba8e0dcf31a9548e9fd928a9416e064ad9417664eadda8d25eb6ad9
HASH:=aeefcd8b3e5b27c22e2e7dfb6ff5e8d0741fd540d96ab355fd00a0472f5884a7
endef
GEOSITE_IRAN_VER:=202409160034

View File

@@ -33,11 +33,7 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) :
val subItem = mActivity.subscriptions[position].second
holder.itemSubSettingBinding.tvName.text = subItem.remarks
holder.itemSubSettingBinding.tvUrl.text = subItem.url
if (subItem.enabled) {
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(R.color.colorAccent)
} else {
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(0)
}
holder.itemSubSettingBinding.chkEnable.isChecked = subItem.enabled
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.itemSubSettingBinding.layoutEdit.setOnClickListener {
@@ -46,10 +42,10 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) :
.putExtra("subId", subId)
)
}
holder.itemSubSettingBinding.infoContainer.setOnClickListener {
subItem.enabled = !subItem.enabled
holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { _, isChecked ->
subItem.enabled = isChecked
subStorage?.encode(subId, Gson().toJson(subItem))
notifyItemChanged(position)
}
if (TextUtils.isEmpty(subItem.url)) {

View File

@@ -15,6 +15,7 @@ import com.v2ray.ang.R
import com.v2ray.ang.dto.*
import com.v2ray.ang.util.MmkvManager.serverRawStorage
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.MmkvManager.subStorage
import com.v2ray.ang.util.fmt.ShadowsocksFmt
import com.v2ray.ang.util.fmt.SocksFmt
import com.v2ray.ang.util.fmt.TrojanFmt
@@ -22,6 +23,7 @@ import com.v2ray.ang.util.fmt.VlessFmt
import com.v2ray.ang.util.fmt.VmessFmt
import com.v2ray.ang.util.fmt.WireguardFmt
import java.lang.reflect.Type
import java.net.URI
import java.util.*
object AngConfigManager {
@@ -397,7 +399,7 @@ object AngConfigManager {
servers.lines()
.forEach { str ->
if (str.startsWith(AppConfig.PROTOCOL_HTTP) || str.startsWith(AppConfig.PROTOCOL_HTTPS)) {
count += MmkvManager.importUrlAsSubscription(str)
count += importUrlAsSubscription(str)
}
}
return count
@@ -577,4 +579,19 @@ object AngConfigManager {
}
return count
}
private fun importUrlAsSubscription(url: String): Int {
val subscriptions = MmkvManager.decodeSubscriptions()
subscriptions.forEach {
if (it.second.url == url) {
return 0
}
}
val uri = URI(Utils.fixIllegalUrl(url))
val subItem = SubscriptionItem()
subItem.remarks = uri.fragment ?: "import sub"
subItem.url = url
subStorage.encode(Utils.getUuid(), Gson().toJson(subItem))
return 1
}
}

View File

@@ -7,7 +7,6 @@ import com.v2ray.ang.dto.ProfileItem
import com.v2ray.ang.dto.ServerAffiliationInfo
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.SubscriptionItem
import java.net.URI
object MmkvManager {
private const val ID_MAIN = "MAIN"
@@ -152,21 +151,6 @@ object MmkvManager {
}
}
fun importUrlAsSubscription(url: String): Int {
val subscriptions = decodeSubscriptions()
subscriptions.forEach {
if (it.second.url == url) {
return 0
}
}
val uri = URI(Utils.fixIllegalUrl(url))
val subItem = SubscriptionItem()
subItem.remarks = uri.fragment ?: "import sub"
subItem.url = url
subStorage.encode(Utils.getUuid(), Gson().toJson(subItem))
return 1
}
fun decodeSubscriptions(): List<Pair<String, SubscriptionItem>> {
val subscriptions = mutableListOf<Pair<String, SubscriptionItem>>()
subStorage.allKeys()?.forEach { key ->
@@ -228,25 +212,6 @@ object MmkvManager {
}
}
fun sortByTestResults() {
data class ServerDelay(var guid: String, var testDelayMillis: Long)
val serverDelays = mutableListOf<ServerDelay>()
val serverList = decodeServerList()
serverList.forEach { key ->
val delay = decodeServerAffiliationInfo(key)?.testDelayMillis ?: 0L
serverDelays.add(ServerDelay(key, if (delay <= 0L) 999999 else delay))
}
serverDelays.sortBy { it.testDelayMillis }
serverDelays.forEach {
serverList.remove(it.guid)
serverList.add(it.guid)
}
encodeServerList(serverList)
}
fun getServerViaRemarks(remarks: String?): ServerConfig? {
if (remarks == null) {
return null

View File

@@ -310,7 +310,22 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
fun sortByTestResults() {
MmkvManager.sortByTestResults()
data class ServerDelay(var guid: String, var testDelayMillis: Long)
val serverDelays = mutableListOf<ServerDelay>()
val serverList = MmkvManager.decodeServerList()
serverList.forEach { key ->
val delay = MmkvManager.decodeServerAffiliationInfo(key)?.testDelayMillis ?: 0L
serverDelays.add(ServerDelay(key, if (delay <= 0L) 999999 else delay))
}
serverDelays.sortBy { it.testDelayMillis }
serverDelays.forEach {
serverList.remove(it.guid)
serverList.add(it.guid)
}
MmkvManager.encodeServerList(serverList)
}

View File

@@ -34,7 +34,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="16dp">
android:paddingStart="@dimen/padding_start">
<TextView
android:layout_width="wrap_content"
@@ -73,7 +73,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingStart="@dimen/padding_start"
android:text="@string/title_configuration_share"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
@@ -97,7 +97,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingStart="@dimen/padding_start"
android:text="@string/title_configuration_restore"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
@@ -129,7 +129,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingStart="@dimen/padding_start"
android:text="@string/title_source_code"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
@@ -153,7 +153,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingStart="@dimen/padding_start"
android:text="@string/title_pref_feedback"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
@@ -178,7 +178,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingStart="@dimen/padding_start"
android:text="@string/title_tg_channel"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
@@ -202,7 +202,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingStart="@dimen/padding_start"
android:text="@string/title_privacy_policy"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>

View File

@@ -11,8 +11,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="20dp"
android:paddingEnd="20dp">
android:paddingStart="@dimen/padding_start"
android:paddingEnd="@dimen/padding_end">
<LinearLayout
android:layout_width="match_parent"
@@ -43,7 +43,7 @@
android:id="@+id/switch_per_app_proxy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="20dp" />
android:paddingStart="@dimen/padding_start" />
</LinearLayout>
@@ -67,7 +67,7 @@
android:id="@+id/switch_bypass_apps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="20dp" />
android:paddingStart="@dimen/padding_start" />
</LinearLayout>
</LinearLayout>

View File

@@ -3,8 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:paddingStart="@dimen/padding_start"
android:paddingEnd="@dimen/padding_end"
tools:context=".ui.LogcatActivity">
<ProgressBar

View File

@@ -77,7 +77,7 @@
android:layout_height="wrap_content"
android:maxLines="2"
android:minLines="1"
android:paddingStart="16dp"
android:paddingStart="@dimen/padding_start"
android:text="@string/connection_test_pending"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />

View File

@@ -86,12 +86,12 @@
android:text="@string/sub_setting_enable" />
<androidx.appcompat.widget.AppCompatCheckBox
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/chk_enable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="6dp" />
android:paddingStart="@dimen/padding_start"
android:paddingEnd="@dimen/padding_end" />
</LinearLayout>
@@ -108,12 +108,12 @@
android:text="@string/sub_auto_update" />
<androidx.appcompat.widget.AppCompatCheckBox
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/auto_update_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="6dp" />
android:paddingStart="@dimen/padding_start"
android:paddingEnd="@dimen/padding_end" />
</LinearLayout>

View File

@@ -11,9 +11,9 @@
android:id="@+id/icon"
android:layout_width="60dp"
android:layout_height="60dp"
android:paddingStart="10dp"
android:paddingStart="@dimen/padding_start"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingEnd="@dimen/padding_end"
android:paddingRight="10dp" />
<LinearLayout
@@ -22,8 +22,8 @@
android:layout_weight="1.0"
android:gravity="center"
android:orientation="vertical"
android:paddingStart="@dimen/layout_margin_right_height"
android:paddingEnd="@dimen/layout_margin_right_height">
android:paddingStart="@dimen/padding_start"
android:paddingEnd="@dimen/padding_end">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/name"
@@ -44,9 +44,9 @@
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false"
android:paddingStart="2dp"
android:paddingStart="@dimen/padding_start"
android:paddingLeft="2dp"
android:paddingEnd="6dp"
android:paddingEnd="@dimen/padding_end"
android:paddingRight="6dp" />
</LinearLayout>

View File

@@ -11,7 +11,7 @@
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="1dp"
android:layout_margin="@dimen/cardview_margin"
app:cardCornerRadius="5dp">
<LinearLayout
@@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingStart="9dp">
android:paddingStart="@dimen/padding_start">
<LinearLayout
android:layout_width="wrap_content"
@@ -80,7 +80,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingEnd="5dp">
android:paddingEnd="@dimen/padding_end">
<TextView
android:id="@+id/tv_subscription"
@@ -172,7 +172,7 @@
android:layout_height="wrap_content"
android:layout_gravity="right"
android:orientation="vertical"
android:paddingEnd="5dp">
android:paddingEnd="@dimen/padding_end">
<TextView
android:id="@+id/tv_type"

View File

@@ -6,18 +6,19 @@
android:layout_height="wrap_content"
android:gravity="center_vertical">
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
<androidx.cardview.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/item_cardview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="1dp"
android:layout_margin="@dimen/cardview_margin"
android:orientation="horizontal"
card_view:cardCornerRadius="5dp">
<LinearLayout
android:id="@+id/info_container"
android:layout_width="match_parent"
android:layout_height="@dimen/server_height"
android:layout_height="@dimen/sub_height"
android:layout_gravity="center"
android:clickable="true"
android:focusable="true"
@@ -26,19 +27,12 @@
android:nextFocusRight="@+id/layout_edit"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/chk_enable"
android:layout_width="6dp"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="vertical" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingStart="9dp">
android:paddingStart="@dimen/padding_start">
<TextView
android:id="@+id/tv_name"
@@ -54,12 +48,25 @@
android:lines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_start"
android:orientation="horizontal">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/chk_enable"
android:text="@string/sub_setting_enable"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="@dimen/server_height"
android:layout_height="@dimen/sub_height"
android:gravity="center"
android:orientation="horizontal">

View File

@@ -8,7 +8,7 @@
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="1dp"
android:layout_margin="@dimen/cardview_margin"
app:cardCornerRadius="5dp">
<LinearLayout
@@ -24,7 +24,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingStart="9dp">
android:paddingStart="@dimen/padding_start">
<LinearLayout
android:layout_width="wrap_content"

View File

@@ -7,7 +7,11 @@
<dimen name="edit_height">50dp</dimen>
<dimen name="png_height">24dp</dimen>
<dimen name="server_height">72dp</dimen>
<dimen name="sub_height">108dp</dimen>
<dimen name="connection_test_height">60dp</dimen>
<dimen name="padding_start">16dp</dimen>
<dimen name="padding_end">16dp</dimen>
<dimen name="cardview_margin">2dp</dimen>
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>

View File

@@ -55,6 +55,9 @@ func (c *Config) GetRequestHeader() http.Header {
}
func (c *Config) WriteResponseHeader(writer http.ResponseWriter) {
// CORS headers for the browser dialer
writer.Header().Set("Access-Control-Allow-Origin", "*")
writer.Header().Set("Access-Control-Allow-Methods", "GET, POST")
paddingLen := c.GetNormalizedXPaddingBytes().roll()
if paddingLen > 0 {
writer.Header().Set("X-Padding", strings.Repeat("0", int(paddingLen)))