diff --git a/.github/update.log b/.github/update.log index b6b0ee9761..ec56ea0824 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1281,3 +1281,4 @@ Update On Fri Feb 20 20:00:01 CET 2026 Update On Sat Feb 21 19:48:30 CET 2026 Update On Sun Feb 22 19:49:09 CET 2026 Update On Mon Feb 23 20:25:31 CET 2026 +Update On Tue Feb 24 20:15:23 CET 2026 diff --git a/brook/README.md b/brook/README.md index d187c4d6f1..dc4af320d7 100644 --- a/brook/README.md +++ b/brook/README.md @@ -2,7 +2,7 @@ A cross-platform programmable network tool. -**Sponsor**: [Shiliew - A network app designed for those who value their time](https://www.txthinking.com/shiliew.html) +**Sponsor**: [Shiliew - Focuses on providing stable network services](https://www.txthinking.com/shiliew.html) ## Server @@ -25,11 +25,9 @@ brook server -l :9999 -p hello - [macOS](https://apps.apple.com/us/app/brook-network-tool/id1216002642) - [About App Mode on macOS](https://www.txthinking.com/talks/articles/macos-app-mode-en.article) - [Windows](https://github.com/txthinking/brook/releases/latest/download/Brook.msix) - - [How to install Brook on Windows](https://www.txthinking.com/talks/articles/msix-brook-en.article) - [Linux](https://github.com/txthinking/brook/releases/latest/download/Brook.bin) - [How to install Brook on Linux](https://www.txthinking.com/talks/articles/linux-app-brook-en.article) - [OpenWrt](https://www.txthinking.com/talks/articles/brook-openwrt-one-en.article) - - [How to install Brook on OpenWrt](https://www.txthinking.com/talks/articles/brook-openwrt-en.article) > You may want to use `brook link` to customize some parameters diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index 3a103c9146..0ea2e1a48c 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -355,7 +355,7 @@ dependencies = [ "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", - "windows-sys 0.52.0", + "windows-sys 0.60.2", "wl-clipboard-rs", "x11rb", ] @@ -869,16 +869,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bincode" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" -dependencies = [ - "serde", - "unty", -] - [[package]] name = "bindgen" version = "0.69.5" @@ -1454,9 +1444,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -1542,7 +1532,6 @@ dependencies = [ "backon", "base64 0.22.1", "bimap", - "bincode 2.0.1", "boa_engine", "boa_utils", "bumpalo", @@ -1603,6 +1592,7 @@ dependencies = [ "parking_lot", "percent-encoding", "port_scanner", + "postcard", "pretty_assertions", "rand 0.9.2", "rayon", @@ -2852,7 +2842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -4511,7 +4501,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1700f6b8b9f00cdd675f32fbb3a5be882213140dfe045805273221ca266c43f8" dependencies = [ - "bincode 1.3.3", + "bincode", "crossbeam-channel", "fnv", "libc", @@ -4875,7 +4865,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.53.3", ] [[package]] @@ -6368,9 +6358,9 @@ dependencies = [ [[package]] name = "oxc_allocator" -version = "0.114.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cb3d0de8b7b87a52e7beafeb468c46c93076253dcac3db533afb94b3535896" +checksum = "e16d4295cf7888893b80ae70ff65c078ae3f9f52d5381cfc7eeffab089e07305" dependencies = [ "allocator-api2", "hashbrown 0.16.1", @@ -6380,9 +6370,9 @@ dependencies = [ [[package]] name = "oxc_ast" -version = "0.114.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c6aa4589029b2417a3d65f785d4b170629c202e575ab4a82127e9df4b90e4f" +checksum = "be755331a7de00100c60e03151663f26037a0dd720be238de57c036be03b4033" dependencies = [ "bitflags 2.10.0", "oxc_allocator", @@ -6397,9 +6387,9 @@ dependencies = [ [[package]] name = "oxc_ast_macros" -version = "0.114.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5953ad22a1d96f822a0b8bbf0b1d1dffba1e443444bd47a94b5efa956b68322f" +checksum = "a13a58adcfaadd4710b4f7d80ad422599ed5bb4956f4747d07e821c5897b16ef" dependencies = [ "phf 0.13.1", "proc-macro2", @@ -6409,9 +6399,9 @@ dependencies = [ [[package]] name = "oxc_ast_visit" -version = "0.114.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d629ff31889443dd2b088cf3d7fbdd3b3179123c5111a0e93e408c400a932f" +checksum = "4e33ffb874949ea07fce9b686c2dba7e221c849e047232c04a84b13bae4496ef" dependencies = [ "oxc_allocator", "oxc_ast", @@ -6421,15 +6411,15 @@ dependencies = [ [[package]] name = "oxc_data_structures" -version = "0.114.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d94ddeaf5d5e740c49fcd0eb8a2d40c7fa4038cd26f337f11e062a03520312" +checksum = "fd6c22a48542899e5f74162d55710ea2f95735c5d3a809196308b2dbf557f434" [[package]] name = "oxc_diagnostics" -version = "0.114.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287468f0f872e1a09bc81a2f6f1c05ecda4cb981274c00240c7dd5b3ba6340b9" +checksum = "fe5961a78ce2a24d288f5e7090f19ce49d062486e0d65e6140d01582198c94fd" dependencies = [ "cow-utils", "oxc-miette", @@ -6438,9 +6428,9 @@ dependencies = [ [[package]] name = "oxc_ecmascript" -version = "0.114.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3cbaa77c89b35825549a062a41753bd4a8e880956dccf3383dcd8e7f3bf246" +checksum = "e1fb3d121c372df31514f95d87c92693001739d2c9e56be37909499b5396faf1" dependencies = [ "cow-utils", "num-bigint", @@ -6454,9 +6444,9 @@ dependencies = [ [[package]] name = "oxc_estree" -version = "0.114.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "137da13e23d3848d25d4686f7fe73d1f7204c53c16adba856cb45e5c39f6f4fb" +checksum = "d38fc12975751e104dc53c369cba1598ff15aa8ca30aaac49e63937256316969" [[package]] name = "oxc_index" @@ -6470,9 +6460,9 @@ dependencies = [ [[package]] name = "oxc_parser" -version = "0.114.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3594e45daea7d4224221d28566bda905349f780266839794bfbd262dbfde0f11" +checksum = "341602ba5eb6629f7f90e49c1fce06bb8eed989412a4178acd8e7f48cf2c7f9d" dependencies = [ "bitflags 2.10.0", "cow-utils", @@ -6493,9 +6483,9 @@ dependencies = [ [[package]] name = "oxc_regular_expression" -version = "0.114.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5860fbe229c1d54556aa5701fd565fc55c85f9846aaa815a8498c6614fab06bb" +checksum = "8e810182cbde172aeada70acc45dae74f6773384e0d295cc27e6e377b1fc277c" dependencies = [ "bitflags 2.10.0", "oxc_allocator", @@ -6509,9 +6499,9 @@ dependencies = [ [[package]] name = "oxc_span" -version = "0.114.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e84bd5f00dc82cf22484b31faec3d746dcae953fd926f36aed33b48c6940aa" +checksum = "b9999ef787b0b989b8c2b31669069d3bdca20d017ff34a7284ff9e983cf7b1d8" dependencies = [ "compact_str", "oxc-miette", @@ -6523,9 +6513,9 @@ dependencies = [ [[package]] name = "oxc_str" -version = "0.114.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11cbab64e0ec125ce0d763ab802b5396f23eca11b0bab5334d4e39f0e60d3b0a" +checksum = "a6fde66bc256ea0d09895c2a56a24f79e76abffd977f6c171516e42f1efdea51" dependencies = [ "compact_str", "hashbrown 0.16.1", @@ -6535,9 +6525,9 @@ dependencies = [ [[package]] name = "oxc_syntax" -version = "0.114.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ab18263d6fa008a34217a3710b79a3a269f223df98081c75eb816bf3a0cca" +checksum = "e77ea5bd4ea42ce05b2f51bcef8e46a4598cea5038ab25877a2d27601a90da83" dependencies = [ "bitflags 2.10.0", "cow-utils", @@ -7288,7 +7278,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -8931,18 +8921,18 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -10531,12 +10521,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "unty" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" - [[package]] name = "ureq" version = "2.12.1" diff --git a/clash-nyanpasu/backend/tauri/Cargo.toml b/clash-nyanpasu/backend/tauri/Cargo.toml index f0cb7797db..09a9cd1f6d 100644 --- a/clash-nyanpasu/backend/tauri/Cargo.toml +++ b/clash-nyanpasu/backend/tauri/Cargo.toml @@ -56,7 +56,7 @@ itertools = "0.14" # swee rayon = "1.10" # for iterator parallel processing ambassador = "0.5.0" # for trait delegation derive_builder = "0.20" # for builder pattern -strum = { version = "0.27", features = ["derive"] } # for enum string conversion +strum = { version = "0.28", features = ["derive"] } # for enum string conversion atomic_enum = "0.3.0" # for atomic enum enumflags2 = "0.7" # for enum flags backon = { version = "1.0.1", features = ["tokio-sleep"] } # for backoff retry @@ -100,10 +100,7 @@ serde_json = { version = "1.0", features = ["preserve_order"] } serde_yaml = { version = "0.10", package = "serde_yaml_ng", branch = "feat/specta-update", git = "https://github.com/libnyanpasu/serde-yaml-ng.git", features = [ "specta", ] } -bincode = { version = "2.0.1", default-features = false, features = [ - "serde", - "std", -] } +postcard = { version = "1.1", features = ["alloc"] } bytes = { version = "1", features = ["serde"] } semver = "1.0" @@ -172,12 +169,12 @@ display-info = "0.5.0" # should be removed after upgrading to tauri v2 # OXC (The Oxidation Compiler) # We use it to parse and transpile the old script profile to esm based script profile -oxc_parser = "0.114" -oxc_allocator = "0.114" -oxc_span = "0.114" -oxc_ast = "0.114" -oxc_syntax = "0.114" -oxc_ast_visit = "0.114" +oxc_parser = "0.115" +oxc_allocator = "0.115" +oxc_span = "0.115" +oxc_ast = "0.115" +oxc_syntax = "0.115" +oxc_ast_visit = "0.115" # Lua Integration mlua = { version = "0.11", features = [ diff --git a/clash-nyanpasu/backend/tauri/src/server/mod.rs b/clash-nyanpasu/backend/tauri/src/server/mod.rs index 6bcfdedb77..30b5e94b9c 100644 --- a/clash-nyanpasu/backend/tauri/src/server/mod.rs +++ b/clash-nyanpasu/backend/tauri/src/server/mod.rs @@ -52,15 +52,14 @@ impl TryFrom> for (HeaderValue, Bytes) { // TODO: use Reader instead of Vec async fn read_cache_file(path: &Path) -> Result<(HeaderValue, Bytes)> { let cache_file = tokio::fs::read(path).await?; - let (cache_file, _): (CacheFile<'static>, _) = - bincode::serde::decode_from_slice(&cache_file, bincode::config::standard())?; + let cache_file: CacheFile<'static> = postcard::from_bytes(&cache_file)?; cache_file.try_into() } // TODO: use Writer instead of Vec async fn write_cache_file(path: &Path, cache_file: &CacheFile<'_>) -> Result<()> { let mut file = tokio::fs::File::create(path).await?; - let cache_file = bincode::serde::encode_to_vec(cache_file, bincode::config::standard())?; + let cache_file = postcard::to_allocvec(cache_file)?; file.write_all(&cache_file).await?; Ok(()) } diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index 46a5bc547a..cf59cea07b 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -34,9 +34,9 @@ "@radix-ui/react-switch": "1.2.6", "@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-use-controllable-state": "1.2.2", - "@tailwindcss/postcss": "4.2.0", + "@tailwindcss/postcss": "4.2.1", "@tanstack/react-table": "8.21.3", - "@tanstack/react-virtual": "3.13.18", + "@tanstack/react-virtual": "3.13.19", "@tanstack/router-zod-adapter": "1.81.5", "@tauri-apps/api": "2.10.1", "@types/json-schema": "7.0.15", @@ -74,7 +74,7 @@ "@csstools/normalize.css": "12.1.1", "@emotion/babel-plugin": "11.13.5", "@emotion/react": "11.14.0", - "@iconify/json": "2.2.441", + "@iconify/json": "2.2.442", "@monaco-editor/react": "4.7.0", "@tanstack/react-query": "5.90.21", "@tanstack/react-router": "1.161.4", diff --git a/clash-nyanpasu/frontend/ui/package.json b/clash-nyanpasu/frontend/ui/package.json index 4f344a11a5..ee39333229 100644 --- a/clash-nyanpasu/frontend/ui/package.json +++ b/clash-nyanpasu/frontend/ui/package.json @@ -29,7 +29,7 @@ "react-error-boundary": "6.0.0", "react-i18next": "15.7.4", "react-use": "17.6.0", - "tailwindcss": "4.2.0", + "tailwindcss": "4.2.1", "vite": "7.3.1", "vite-tsconfig-paths": "6.1.1" }, diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index 67e5083102..218a4de7a9 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -2,7 +2,7 @@ "manifest_version": 1, "latest": { "mihomo": "v1.19.20", - "mihomo_alpha": "alpha-3752cb0", + "mihomo_alpha": "Not Found", "clash_rs": "v0.9.4", "clash_premium": "2023-09-05-gdcc8d87", "clash_rs_alpha": "0.9.4-alpha+sha.c39252b" @@ -69,5 +69,5 @@ "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" } }, - "updated_at": "2026-02-22T22:22:22.101Z" + "updated_at": "2026-02-23T22:26:50.433Z" } diff --git a/clash-nyanpasu/package.json b/clash-nyanpasu/package.json index 60d56f821d..f92c71cc0c 100644 --- a/clash-nyanpasu/package.json +++ b/clash-nyanpasu/package.json @@ -71,7 +71,7 @@ "knip": "5.85.0", "lint-staged": "16.2.7", "npm-run-all2": "8.0.4", - "oxlint": "1.49.0", + "oxlint": "1.50.0", "postcss": "8.5.6", "postcss-html": "1.8.1", "postcss-import": "16.1.1", @@ -87,11 +87,11 @@ "stylelint-declaration-block-no-ignored-properties": "3.0.0", "stylelint-order": "7.0.1", "stylelint-scss": "7.0.0", - "tailwindcss": "4.2.0", + "tailwindcss": "4.2.1", "tsx": "4.21.0", "typescript": "5.9.3" }, - "packageManager": "pnpm@10.30.1", + "packageManager": "pnpm@10.30.2", "engines": { "node": "24.13.1" }, diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index bc41727551..4f36070b1c 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -68,8 +68,8 @@ importers: specifier: 8.0.4 version: 8.0.4 oxlint: - specifier: 1.49.0 - version: 1.49.0 + specifier: 1.50.0 + version: 1.50.0 postcss: specifier: 8.5.6 version: 8.5.6 @@ -116,8 +116,8 @@ importers: specifier: 7.0.0 version: 7.0.0(stylelint@17.3.0(typescript@5.9.3)) tailwindcss: - specifier: 4.2.0 - version: 4.2.0 + specifier: 4.2.1 + version: 4.2.1 tsx: specifier: 4.21.0 version: 4.21.0 @@ -234,14 +234,14 @@ importers: specifier: 1.2.2 version: 1.2.2(@types/react@19.2.14)(react@19.2.4) '@tailwindcss/postcss': - specifier: 4.2.0 - version: 4.2.0 + specifier: 4.2.1 + version: 4.2.1 '@tanstack/react-table': specifier: 8.21.3 version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-virtual': - specifier: 3.13.18 - version: 3.13.18(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 3.13.19 + version: 3.13.19(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/router-zod-adapter': specifier: 1.81.5 version: 1.81.5(@tanstack/react-router@1.161.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6) @@ -349,8 +349,8 @@ importers: specifier: 11.14.0 version: 11.14.0(@types/react@19.2.14)(react@19.2.4) '@iconify/json': - specifier: 2.2.441 - version: 2.2.441 + specifier: 2.2.442 + version: 2.2.442 '@monaco-editor/react': specifier: 4.7.0 version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -520,8 +520,8 @@ importers: specifier: 17.6.0 version: 17.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) tailwindcss: - specifier: 4.2.0 - version: 4.2.0 + specifier: 4.2.1 + version: 4.2.1 vite: specifier: 7.3.1 version: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1) @@ -1683,8 +1683,8 @@ packages: prettier-plugin-ember-template-tag: optional: true - '@iconify/json@2.2.441': - resolution: {integrity: sha512-SN7n/3vboWO7A2iTV/t5RP5Hp0dSJkwpeQ/FsksN3sV8BsV0WuAhJp0n4HmXoAD+Icu0/cM07a8ijt+qSDf5Cg==} + '@iconify/json@2.2.442': + resolution: {integrity: sha512-JvAEHLuOu9aS1Ch0kLCs6hlltRUFrs7JVD0Ic6Xfxj/JOkJCCwW0dRCHILEwkWFXrKTmDAP5XTUsCItsfYxdkg==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -2407,124 +2407,124 @@ packages: cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.49.0': - resolution: {integrity: sha512-2WPoh/2oK9r/i2R4o4J18AOrm3HVlWiHZ8TnuCaS4dX8m5ZzRmHW0I3eLxEurQLHWVruhQN7fHgZnah+ag5iQg==} + '@oxlint/binding-android-arm-eabi@1.50.0': + resolution: {integrity: sha512-G7MRGk/6NCe+L8ntonRdZP7IkBfEpiZ/he3buLK6JkLgMHgJShXZ+BeOwADmspXez7U7F7L1Anf4xLSkLHiGTg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.49.0': - resolution: {integrity: sha512-YqJAGvNB11EzoKm1euVhZntb79alhMvWW/j12bYqdvVxn6xzEQWrEDCJg9BPo3A3tBCSUBKH7bVkAiCBqK/L1w==} + '@oxlint/binding-android-arm64@1.50.0': + resolution: {integrity: sha512-GeSuMoJWCVpovJi/e3xDSNgjeR8WEZ6MCXL6EtPiCIM2NTzv7LbflARINTXTJy2oFBYyvdf/l2PwHzYo6EdXvg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.49.0': - resolution: {integrity: sha512-WFocCRlvVkMhChCJ2qpJfp1Gj/IjvyjuifH9Pex8m8yHonxxQa3d8DZYreuDQU3T4jvSY8rqhoRqnpc61Nlbxw==} + '@oxlint/binding-darwin-arm64@1.50.0': + resolution: {integrity: sha512-w3SY5YtxGnxCHPJ8Twl3KmS9oja1gERYk3AMoZ7Hv8P43ZtB6HVfs02TxvarxfL214Tm3uzvc2vn+DhtUNeKnw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.49.0': - resolution: {integrity: sha512-BN0KniwvehbUfYztOMwEDkYoojGm/narf5oJf+/ap+6PnzMeWLezMaVARNIS0j3OdMkjHTEP8s3+GdPJ7WDywQ==} + '@oxlint/binding-darwin-x64@1.50.0': + resolution: {integrity: sha512-hNfogDqy7tvmllXKBSlHo6k5x7dhTUVOHbMSE15CCAcXzmqf5883aPvBYPOq9AE7DpDUQUZ1kVE22YbiGW+tuw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.49.0': - resolution: {integrity: sha512-SnkAc/DPIY6joMCiP/+53Q+N2UOGMU6ULvbztpmvPJNF/jYPGhNbKtN982uj2Gs6fpbxYkmyj08QnpkD4fbHJA==} + '@oxlint/binding-freebsd-x64@1.50.0': + resolution: {integrity: sha512-ykZevOWEyu0nsxolA911ucxpEv0ahw8jfEeGWOwwb/VPoE4xoexuTOAiPNlWZNJqANlJl7yp8OyzCtXTUAxotw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.49.0': - resolution: {integrity: sha512-6Z3EzRvpQVIpO7uFhdiGhdE8Mh3S2VWKLL9xuxVqD6fzPhyI3ugthpYXlCChXzO8FzcYIZ3t1+Kau+h2NY1hqA==} + '@oxlint/binding-linux-arm-gnueabihf@1.50.0': + resolution: {integrity: sha512-hif3iDk7vo5GGJ4OLCCZAf2vjnU9FztGw4L0MbQL0M2iY9LKFtDMMiQAHmkF0PQGQMVbTYtPdXCLKVgdkiqWXQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.49.0': - resolution: {integrity: sha512-wdjXaQYAL/L25732mLlngfst4Jdmi/HLPVHb3yfCoP5mE3lO/pFFrmOJpqWodgv29suWY74Ij+RmJ/YIG5VuzQ==} + '@oxlint/binding-linux-arm-musleabihf@1.50.0': + resolution: {integrity: sha512-dVp9iSssiGAnTNey2Ruf6xUaQhdnvcFOJyRWd/mu5o2jVbFK15E5fbWGeFRfmuobu5QXuROtFga44+7DOS3PLg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.49.0': - resolution: {integrity: sha512-oSHpm8zmSvAG1BWUumbDRSg7moJbnwoEXKAkwDf/xTQJOzvbUknq95NVQdw/AduZr5dePftalB8rzJNGBogUMg==} + '@oxlint/binding-linux-arm64-gnu@1.50.0': + resolution: {integrity: sha512-1cT7yz2HA910CKA9NkH1ZJo50vTtmND2fkoW1oyiSb0j6WvNtJ0Wx2zoySfXWc/c+7HFoqRK5AbEoL41LOn9oA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.49.0': - resolution: {integrity: sha512-xeqkMOARgGBlEg9BQuPDf6ZW711X6BT5qjDyeM5XNowCJeTSdmMhpePJjTEiVbbr3t21sIlK8RE6X5bc04nWyQ==} + '@oxlint/binding-linux-arm64-musl@1.50.0': + resolution: {integrity: sha512-++B3k/HEPFVlj89cOz8kWfQccMZB/aWL9AhsW7jPIkG++63Mpwb2cE9XOEsd0PATbIan78k2Gky+09uWM1d/gQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.49.0': - resolution: {integrity: sha512-uvcqRO6PnlJGbL7TeePhTK5+7/JXbxGbN+C6FVmfICDeeRomgQqrfVjf0lUrVpUU8ii8TSkIbNdft3M+oNlOsQ==} + '@oxlint/binding-linux-ppc64-gnu@1.50.0': + resolution: {integrity: sha512-Z9b/KpFMkx66w3gVBqjIC1AJBTZAGoI9+U+K5L4QM0CB/G0JSNC1es9b3Y0Vcrlvtdn8A+IQTkYjd/Q0uCSaZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.49.0': - resolution: {integrity: sha512-Dw1HkdXAwHNH+ZDserHP2RzXQmhHtpsYYI0hf8fuGAVCIVwvS6w1+InLxpPMY25P8ASRNiFN3hADtoh6lI+4lg==} + '@oxlint/binding-linux-riscv64-gnu@1.50.0': + resolution: {integrity: sha512-jvmuIw8wRSohsQlFNIST5uUwkEtEJmOQYr33bf/K2FrFPXHhM4KqGekI3ShYJemFS/gARVacQFgBzzJKCAyJjg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.49.0': - resolution: {integrity: sha512-EPlMYaA05tJ9km/0dI9K57iuMq3Tw+nHst7TNIegAJZrBPtsOtYaMFZEaWj02HA8FI5QvSnRHMt+CI+RIhXJBQ==} + '@oxlint/binding-linux-riscv64-musl@1.50.0': + resolution: {integrity: sha512-x+UrN47oYNh90nmAAyql8eQaaRpHbDPu5guasDg10+OpszUQ3/1+1J6zFMmV4xfIEgTcUXG/oI5fxJhF4eWCNA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.49.0': - resolution: {integrity: sha512-yZiQL9qEwse34aMbnMb5VqiAWfDY+fLFuoJbHOuzB1OaJZbN1MRF9Nk+W89PIpGr5DNPDipwjZb8+Q7wOywoUQ==} + '@oxlint/binding-linux-s390x-gnu@1.50.0': + resolution: {integrity: sha512-i/JLi2ljLUIVfekMj4ISmdt+Hn11wzYUdRRrkVUYsCWw7zAy5xV7X9iA+KMyM156LTFympa7s3oKBjuCLoTAUQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.49.0': - resolution: {integrity: sha512-CcCDwMMXSchNkhdgvhVn3DLZ4EnBXAD8o8+gRzahg+IdSt/72y19xBgShJgadIRF0TsRcV/MhDUMwL5N/W54aQ==} + '@oxlint/binding-linux-x64-gnu@1.50.0': + resolution: {integrity: sha512-/C7brhn6c6UUPccgSPCcpLQXcp+xKIW/3sji/5VZ8/OItL3tQ2U7KalHz887UxxSQeEOmd1kY6lrpuwFnmNqOA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.49.0': - resolution: {integrity: sha512-u3HfKV8BV6t6UCCbN0RRiyqcymhrnpunVmLFI8sEa5S/EBu+p/0bJ3D7LZ2KT6PsBbrB71SWq4DeFrskOVgIZg==} + '@oxlint/binding-linux-x64-musl@1.50.0': + resolution: {integrity: sha512-oDR1f+bGOYU8LfgtEW8XtotWGB63ghtcxk5Jm6IDTCk++rTA/IRMsjOid2iMd+1bW+nP9Mdsmcdc7VbPD3+iyQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.49.0': - resolution: {integrity: sha512-dRDpH9fw+oeUMpM4br0taYCFpW6jQtOuEIec89rOgDA1YhqwmeRcx0XYeCv7U48p57qJ1XZHeMGM9LdItIjfzA==} + '@oxlint/binding-openharmony-arm64@1.50.0': + resolution: {integrity: sha512-4CmRGPp5UpvXyu4jjP9Tey/SrXDQLRvZXm4pb4vdZBxAzbFZkCyh0KyRy4txld/kZKTJlW4TO8N1JKrNEk+mWw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.49.0': - resolution: {integrity: sha512-6rrKe/wL9tn0qnOy76i1/0f4Dc3dtQnibGlU4HqR/brVHlVjzLSoaH0gAFnLnznh9yQ6gcFTBFOPrcN/eKPDGA==} + '@oxlint/binding-win32-arm64-msvc@1.50.0': + resolution: {integrity: sha512-Fq0M6vsGcFsSfeuWAACDhd5KJrO85ckbEfe1EGuBj+KPyJz7KeWte2fSFrFGmNKNXyhEMyx4tbgxiWRujBM2KQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.49.0': - resolution: {integrity: sha512-CXHLWAtLs2xG/aVy1OZiYJzrULlq0QkYpI6cd7VKMrab+qur4fXVE/B1Bp1m0h1qKTj5/FTGg6oU4qaXMjS/ug==} + '@oxlint/binding-win32-ia32-msvc@1.50.0': + resolution: {integrity: sha512-qTdWR9KwY/vxJGhHVIZG2eBOhidOQvOwzDxnX+jhW/zIVacal1nAhR8GLkiywW8BIFDkQKXo/zOfT+/DY+ns/w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.49.0': - resolution: {integrity: sha512-VteIelt78kwzSglOozaQcs6BCS4Lk0j+QA+hGV0W8UeyaqQ3XpbZRhDU55NW1PPvCy1tg4VXsTlEaPovqto7nQ==} + '@oxlint/binding-win32-x64-msvc@1.50.0': + resolution: {integrity: sha512-682t7npLC4G2Ca+iNlI9fhAKTcFPYYXJjwoa88H4q+u5HHHlsnL/gHULapX3iqp+A8FIJbgdylL5KMYo2LaluQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -3377,69 +3377,69 @@ packages: '@swc/types@0.1.25': resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} - '@tailwindcss/node@4.2.0': - resolution: {integrity: sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q==} + '@tailwindcss/node@4.2.1': + resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==} - '@tailwindcss/oxide-android-arm64@4.2.0': - resolution: {integrity: sha512-F0QkHAVaW/JNBWl4CEKWdZ9PMb0khw5DCELAOnu+RtjAfx5Zgw+gqCHFvqg3AirU1IAd181fwOtJQ5I8Yx5wtw==} + '@tailwindcss/oxide-android-arm64@4.2.1': + resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==} engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.2.0': - resolution: {integrity: sha512-I0QylkXsBsJMZ4nkUNSR04p6+UptjcwhcVo3Zu828ikiEqHjVmQL9RuQ6uT/cVIiKpvtVA25msu/eRV97JeNSA==} + '@tailwindcss/oxide-darwin-arm64@4.2.1': + resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==} engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.2.0': - resolution: {integrity: sha512-6TmQIn4p09PBrmnkvbYQ0wbZhLtbaksCDx7Y7R3FYYx0yxNA7xg5KP7dowmQ3d2JVdabIHvs3Hx4K3d5uCf8xg==} + '@tailwindcss/oxide-darwin-x64@4.2.1': + resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==} engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.2.0': - resolution: {integrity: sha512-qBudxDvAa2QwGlq9y7VIzhTvp2mLJ6nD/G8/tI70DCDoneaUeLWBJaPcbfzqRIWraj+o969aDQKvKW9dvkUizw==} + '@tailwindcss/oxide-freebsd-x64@4.2.1': + resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==} engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.0': - resolution: {integrity: sha512-7XKkitpy5NIjFZNUQPeUyNJNJn1CJeV7rmMR+exHfTuOsg8rxIO9eNV5TSEnqRcaOK77zQpsyUkBWmPy8FgdSg==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': + resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==} engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.2.0': - resolution: {integrity: sha512-Mff5a5Q3WoQR01pGU1gr29hHM1N93xYrKkGXfPw/aRtK4bOc331Ho4Tgfsm5WDGvpevqMpdlkCojT3qlCQbCpA==} + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': + resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-arm64-musl@4.2.0': - resolution: {integrity: sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A==} + '@tailwindcss/oxide-linux-arm64-musl@4.2.1': + resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [musl] - '@tailwindcss/oxide-linux-x64-gnu@4.2.0': - resolution: {integrity: sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA==} + '@tailwindcss/oxide-linux-x64-gnu@4.2.1': + resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-x64-musl@4.2.0': - resolution: {integrity: sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw==} + '@tailwindcss/oxide-linux-x64-musl@4.2.1': + resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [musl] - '@tailwindcss/oxide-wasm32-wasi@4.2.0': - resolution: {integrity: sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw==} + '@tailwindcss/oxide-wasm32-wasi@4.2.1': + resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -3450,24 +3450,24 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.2.0': - resolution: {integrity: sha512-2UU/15y1sWDEDNJXxEIrfWKC2Yb4YgIW5Xz2fKFqGzFWfoMHWFlfa1EJlGO2Xzjkq/tvSarh9ZTjvbxqWvLLXA==} + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': + resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==} engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.2.0': - resolution: {integrity: sha512-CrFadmFoc+z76EV6LPG1jx6XceDsaCG3lFhyLNo/bV9ByPrE+FnBPckXQVP4XRkN76h3Fjt/a+5Er/oA/nCBvQ==} + '@tailwindcss/oxide-win32-x64-msvc@4.2.1': + resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==} engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.2.0': - resolution: {integrity: sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ==} + '@tailwindcss/oxide@4.2.1': + resolution: {integrity: sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==} engines: {node: '>= 20'} - '@tailwindcss/postcss@4.2.0': - resolution: {integrity: sha512-u6YBacGpOm/ixPfKqfgrJEjMfrYmPD7gEFRoygS/hnQaRtV0VCBdpkx5Ouw9pnaLRwwlgGCuJw8xLpaR0hOrQg==} + '@tailwindcss/postcss@4.2.1': + resolution: {integrity: sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw==} '@tanstack/history@1.161.4': resolution: {integrity: sha512-Kp/WSt411ZWYvgXy6uiv5RmhHrz9cAml05AQPrtdAp7eUqvIDbMGPnML25OKbzR3RJ1q4wgENxDTvlGPa9+Mww==} @@ -3517,8 +3517,8 @@ packages: react: '>=16.8' react-dom: '>=16.8' - '@tanstack/react-virtual@3.13.18': - resolution: {integrity: sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A==} + '@tanstack/react-virtual@3.13.19': + resolution: {integrity: sha512-KzwmU1IbE0IvCZSm6OXkS+kRdrgW2c2P3Ho3NC+zZXWK6oObv/L+lcV/2VuJ+snVESRlMJ+w/fg4WXI/JzoNGQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3586,8 +3586,8 @@ packages: resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} engines: {node: '>=12'} - '@tanstack/virtual-core@3.13.18': - resolution: {integrity: sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==} + '@tanstack/virtual-core@3.13.19': + resolution: {integrity: sha512-/BMP7kNhzKOd7wnDeB8NrIRNLwkf5AhCYCvtfZV2GXWbBieFm/el0n6LOAXlTi6ZwHICSNnQcIxRCWHrLzDY+g==} '@tanstack/virtual-core@3.13.9': resolution: {integrity: sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ==} @@ -6084,8 +6084,8 @@ packages: oxc-resolver@11.16.1: resolution: {integrity: sha512-eN5tLRx2oyyNpPyWWItuPiNLY/EpGlqXVJll+Uptds4owQ4UQK3T8rPlR+wl8xBKvQZM4drFC1nKVLeZDUWpQg==} - oxlint@1.49.0: - resolution: {integrity: sha512-YZffp0gM+63CJoRhHjtjRnwKtAgUnXM6j63YQ++aigji2NVvLGsUlrXo9gJUXZOdcbfShLYtA6RuTu8GZ4lzOQ==} + oxlint@1.50.0: + resolution: {integrity: sha512-iSJ4IZEICBma8cZX7kxIIz9PzsYLF2FaLAYN6RKu7VwRVKdu7RIgpP99bTZaGl//Yao7fsaGZLSEo5xBrI5ReQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -7095,8 +7095,8 @@ packages: tailwind-merge@3.5.0: resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} - tailwindcss@4.2.0: - resolution: {integrity: sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q==} + tailwindcss@4.2.1: + resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==} tapable@2.3.0: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} @@ -8887,7 +8887,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@iconify/json@2.2.441': + '@iconify/json@2.2.442': dependencies: '@iconify/types': 2.0.0 pathe: 2.0.3 @@ -9619,61 +9619,61 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.16.1': optional: true - '@oxlint/binding-android-arm-eabi@1.49.0': + '@oxlint/binding-android-arm-eabi@1.50.0': optional: true - '@oxlint/binding-android-arm64@1.49.0': + '@oxlint/binding-android-arm64@1.50.0': optional: true - '@oxlint/binding-darwin-arm64@1.49.0': + '@oxlint/binding-darwin-arm64@1.50.0': optional: true - '@oxlint/binding-darwin-x64@1.49.0': + '@oxlint/binding-darwin-x64@1.50.0': optional: true - '@oxlint/binding-freebsd-x64@1.49.0': + '@oxlint/binding-freebsd-x64@1.50.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.49.0': + '@oxlint/binding-linux-arm-gnueabihf@1.50.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.49.0': + '@oxlint/binding-linux-arm-musleabihf@1.50.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.49.0': + '@oxlint/binding-linux-arm64-gnu@1.50.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.49.0': + '@oxlint/binding-linux-arm64-musl@1.50.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.49.0': + '@oxlint/binding-linux-ppc64-gnu@1.50.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.49.0': + '@oxlint/binding-linux-riscv64-gnu@1.50.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.49.0': + '@oxlint/binding-linux-riscv64-musl@1.50.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.49.0': + '@oxlint/binding-linux-s390x-gnu@1.50.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.49.0': + '@oxlint/binding-linux-x64-gnu@1.50.0': optional: true - '@oxlint/binding-linux-x64-musl@1.49.0': + '@oxlint/binding-linux-x64-musl@1.50.0': optional: true - '@oxlint/binding-openharmony-arm64@1.49.0': + '@oxlint/binding-openharmony-arm64@1.50.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.49.0': + '@oxlint/binding-win32-arm64-msvc@1.50.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.49.0': + '@oxlint/binding-win32-ia32-msvc@1.50.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.49.0': + '@oxlint/binding-win32-x64-msvc@1.50.0': optional: true '@parcel/watcher-android-arm64@2.4.1': @@ -10432,7 +10432,7 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tailwindcss/node@4.2.0': + '@tailwindcss/node@4.2.1': dependencies: '@jridgewell/remapping': 2.3.5 enhanced-resolve: 5.19.0 @@ -10440,66 +10440,66 @@ snapshots: lightningcss: 1.31.1 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.2.0 + tailwindcss: 4.2.1 - '@tailwindcss/oxide-android-arm64@4.2.0': + '@tailwindcss/oxide-android-arm64@4.2.1': optional: true - '@tailwindcss/oxide-darwin-arm64@4.2.0': + '@tailwindcss/oxide-darwin-arm64@4.2.1': optional: true - '@tailwindcss/oxide-darwin-x64@4.2.0': + '@tailwindcss/oxide-darwin-x64@4.2.1': optional: true - '@tailwindcss/oxide-freebsd-x64@4.2.0': + '@tailwindcss/oxide-freebsd-x64@4.2.1': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.0': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.2.0': + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.2.0': + '@tailwindcss/oxide-linux-arm64-musl@4.2.1': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.2.0': + '@tailwindcss/oxide-linux-x64-gnu@4.2.1': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.2.0': + '@tailwindcss/oxide-linux-x64-musl@4.2.1': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.2.0': + '@tailwindcss/oxide-wasm32-wasi@4.2.1': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.2.0': + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.2.0': + '@tailwindcss/oxide-win32-x64-msvc@4.2.1': optional: true - '@tailwindcss/oxide@4.2.0': + '@tailwindcss/oxide@4.2.1': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.0 - '@tailwindcss/oxide-darwin-arm64': 4.2.0 - '@tailwindcss/oxide-darwin-x64': 4.2.0 - '@tailwindcss/oxide-freebsd-x64': 4.2.0 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.0 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.0 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.0 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.0 - '@tailwindcss/oxide-linux-x64-musl': 4.2.0 - '@tailwindcss/oxide-wasm32-wasi': 4.2.0 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.0 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.0 + '@tailwindcss/oxide-android-arm64': 4.2.1 + '@tailwindcss/oxide-darwin-arm64': 4.2.1 + '@tailwindcss/oxide-darwin-x64': 4.2.1 + '@tailwindcss/oxide-freebsd-x64': 4.2.1 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.1 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.1 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.1 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.1 + '@tailwindcss/oxide-linux-x64-musl': 4.2.1 + '@tailwindcss/oxide-wasm32-wasi': 4.2.1 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.1 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.1 - '@tailwindcss/postcss@4.2.0': + '@tailwindcss/postcss@4.2.1': dependencies: '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.2.0 - '@tailwindcss/oxide': 4.2.0 + '@tailwindcss/node': 4.2.1 + '@tailwindcss/oxide': 4.2.1 postcss: 8.5.6 - tailwindcss: 4.2.0 + tailwindcss: 4.2.1 '@tanstack/history@1.161.4': {} @@ -10549,9 +10549,9 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@tanstack/react-virtual@3.13.18(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-virtual@3.13.19(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@tanstack/virtual-core': 3.13.18 + '@tanstack/virtual-core': 3.13.19 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -10637,7 +10637,7 @@ snapshots: '@tanstack/table-core@8.21.3': {} - '@tanstack/virtual-core@3.13.18': {} + '@tanstack/virtual-core@3.13.19': {} '@tanstack/virtual-core@3.13.9': {} @@ -13338,27 +13338,27 @@ snapshots: '@oxc-resolver/binding-win32-ia32-msvc': 11.16.1 '@oxc-resolver/binding-win32-x64-msvc': 11.16.1 - oxlint@1.49.0: + oxlint@1.50.0: optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.49.0 - '@oxlint/binding-android-arm64': 1.49.0 - '@oxlint/binding-darwin-arm64': 1.49.0 - '@oxlint/binding-darwin-x64': 1.49.0 - '@oxlint/binding-freebsd-x64': 1.49.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.49.0 - '@oxlint/binding-linux-arm-musleabihf': 1.49.0 - '@oxlint/binding-linux-arm64-gnu': 1.49.0 - '@oxlint/binding-linux-arm64-musl': 1.49.0 - '@oxlint/binding-linux-ppc64-gnu': 1.49.0 - '@oxlint/binding-linux-riscv64-gnu': 1.49.0 - '@oxlint/binding-linux-riscv64-musl': 1.49.0 - '@oxlint/binding-linux-s390x-gnu': 1.49.0 - '@oxlint/binding-linux-x64-gnu': 1.49.0 - '@oxlint/binding-linux-x64-musl': 1.49.0 - '@oxlint/binding-openharmony-arm64': 1.49.0 - '@oxlint/binding-win32-arm64-msvc': 1.49.0 - '@oxlint/binding-win32-ia32-msvc': 1.49.0 - '@oxlint/binding-win32-x64-msvc': 1.49.0 + '@oxlint/binding-android-arm-eabi': 1.50.0 + '@oxlint/binding-android-arm64': 1.50.0 + '@oxlint/binding-darwin-arm64': 1.50.0 + '@oxlint/binding-darwin-x64': 1.50.0 + '@oxlint/binding-freebsd-x64': 1.50.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.50.0 + '@oxlint/binding-linux-arm-musleabihf': 1.50.0 + '@oxlint/binding-linux-arm64-gnu': 1.50.0 + '@oxlint/binding-linux-arm64-musl': 1.50.0 + '@oxlint/binding-linux-ppc64-gnu': 1.50.0 + '@oxlint/binding-linux-riscv64-gnu': 1.50.0 + '@oxlint/binding-linux-riscv64-musl': 1.50.0 + '@oxlint/binding-linux-s390x-gnu': 1.50.0 + '@oxlint/binding-linux-x64-gnu': 1.50.0 + '@oxlint/binding-linux-x64-musl': 1.50.0 + '@oxlint/binding-openharmony-arm64': 1.50.0 + '@oxlint/binding-win32-arm64-msvc': 1.50.0 + '@oxlint/binding-win32-ia32-msvc': 1.50.0 + '@oxlint/binding-win32-x64-msvc': 1.50.0 p-retry@7.1.1: dependencies: @@ -14316,7 +14316,7 @@ snapshots: tailwind-merge@3.5.0: {} - tailwindcss@4.2.0: {} + tailwindcss@4.2.1: {} tapable@2.3.0: {} diff --git a/lede/include/kernel-5.10 b/lede/include/kernel-5.10 index cfd85f3d05..2b57d8fe53 100644 --- a/lede/include/kernel-5.10 +++ b/lede/include/kernel-5.10 @@ -1,2 +1,2 @@ -LINUX_VERSION-5.10 = .249 -LINUX_KERNEL_HASH-5.10.249 = b990ed34261149542f932e5eaf1ba58535c5d48c147ae36bc2747c65e54d1046 +LINUX_VERSION-5.10 = .251 +LINUX_KERNEL_HASH-5.10.251 = e6857625fee3b587b0279b445adc3940a5c40723385fa1055ac7af16ff4b4c01 diff --git a/lede/include/kernel-5.15 b/lede/include/kernel-5.15 index 9c502e074a..063c9f9573 100644 --- a/lede/include/kernel-5.15 +++ b/lede/include/kernel-5.15 @@ -1,2 +1,2 @@ -LINUX_VERSION-5.15 = .199 -LINUX_KERNEL_HASH-5.15.199 = 01c0d3ae4c138a51891cb54d21374259fd07bfe1076e06362ec0ae0a822fbd2e +LINUX_VERSION-5.15 = .201 +LINUX_KERNEL_HASH-5.15.201 = 4f2afffbeddaad6b8527d41a3e3a82646d3cf5dfd0acbb6c4e8a99fc70461b96 diff --git a/lede/include/kernel-6.1 b/lede/include/kernel-6.1 index 9ea22f5635..1e62805fc2 100644 --- a/lede/include/kernel-6.1 +++ b/lede/include/kernel-6.1 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.1 = .162 -LINUX_KERNEL_HASH-6.1.162 = 9766422a49b9a24ea911ebc0b4e7d1f3d84fba3bc374387ae3c4bc60bec6d089 +LINUX_VERSION-6.1 = .164 +LINUX_KERNEL_HASH-6.1.164 = 33bf087f7bbf7f626873dd7d955eb44182a93695db41f5f89a6bd3d233a39d1c diff --git a/lede/include/kernel-6.12 b/lede/include/kernel-6.12 index f938fad24a..3b9c474086 100644 --- a/lede/include/kernel-6.12 +++ b/lede/include/kernel-6.12 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.12 = .69 -LINUX_KERNEL_HASH-6.12.69 = 4b493657f218703239c4f22415f027b3644949bf2761abd18b849f0aad5f7665 +LINUX_VERSION-6.12 = .74 +LINUX_KERNEL_HASH-6.12.74 = 3b56eeb1dc9a437f189ca56b823be3769994f59a4ea0895b08ec0d20acaca13e diff --git a/lede/include/kernel-6.6 b/lede/include/kernel-6.6 index 06ba2d75d1..55a5b8c57c 100644 --- a/lede/include/kernel-6.6 +++ b/lede/include/kernel-6.6 @@ -1,2 +1,2 @@ -LINUX_VERSION-6.6 = .123 -LINUX_KERNEL_HASH-6.6.123 = a616d4b5a18a9097e2df8e03dfd2ba366ddfc1321b16d648651dc512b3e945b1 +LINUX_VERSION-6.6 = .127 +LINUX_KERNEL_HASH-6.6.127 = a7cd9c97b4f0b31cc030bcdc60abe5434fffb2556e293f7438ce7909dff8c9fe diff --git a/lede/include/version.mk b/lede/include/version.mk index abd7bce7f3..9bd9af7384 100644 --- a/lede/include/version.mk +++ b/lede/include/version.mk @@ -23,13 +23,13 @@ PKG_CONFIG_DEPENDS += \ sanitize = $(call tolower,$(subst _,-,$(subst $(space),-,$(1)))) VERSION_NUMBER:=$(call qstrip,$(CONFIG_VERSION_NUMBER)) -VERSION_NUMBER:=$(if $(VERSION_NUMBER),$(VERSION_NUMBER),24.10.3) +VERSION_NUMBER:=$(if $(VERSION_NUMBER),$(VERSION_NUMBER),24.10.5) VERSION_CODE:=$(call qstrip,$(CONFIG_VERSION_CODE)) VERSION_CODE:=$(if $(VERSION_CODE),$(VERSION_CODE),$(REVISION)) VERSION_REPO:=$(call qstrip,$(CONFIG_VERSION_REPO)) -VERSION_REPO:=$(if $(VERSION_REPO),$(VERSION_REPO),https://downloads.openwrt.org/releases/24.10.3) +VERSION_REPO:=$(if $(VERSION_REPO),$(VERSION_REPO),https://downloads.openwrt.org/releases/24.10.5) VERSION_DIST:=$(call qstrip,$(CONFIG_VERSION_DIST)) VERSION_DIST:=$(if $(VERSION_DIST),$(VERSION_DIST),OpenWrt) diff --git a/lede/package/base-files/image-config.in b/lede/package/base-files/image-config.in index 73c62b349a..135afac61b 100644 --- a/lede/package/base-files/image-config.in +++ b/lede/package/base-files/image-config.in @@ -190,7 +190,7 @@ if VERSIONOPT config VERSION_REPO string prompt "Release repository" - default "https://downloads.openwrt.org/releases/24.10.3" + default "https://downloads.openwrt.org/releases/24.10.5" help This is the repository address embedded in the image, it defaults to the trunk snapshot repo; the url may contain the following placeholders: diff --git a/lede/package/firmware/linux-firmware/Makefile b/lede/package/firmware/linux-firmware/Makefile index e94c6378d7..8823116290 100644 --- a/lede/package/firmware/linux-firmware/Makefile +++ b/lede/package/firmware/linux-firmware/Makefile @@ -8,12 +8,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=linux-firmware -PKG_VERSION:=20251111 +PKG_VERSION:=20260221 PKG_RELEASE:=1 PKG_SOURCE_URL:=@KERNEL/linux/kernel/firmware PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz -PKG_HASH:=ab57a1526595090bb4874c35335e2252288dcbea546eff491654b2313438a47d +PKG_HASH:=bd19acc4c1a02548e09d3df67f987fe6e378df735bab138c1d9e917962056d94 PKG_MAINTAINER:=Felix Fietkau diff --git a/lede/package/kernel/mac80211/realtek.mk b/lede/package/kernel/mac80211/realtek.mk index 7358e209fb..71e173baf6 100644 --- a/lede/package/kernel/mac80211/realtek.mk +++ b/lede/package/kernel/mac80211/realtek.mk @@ -4,7 +4,8 @@ PKG_DRIVERS += \ rtl8xxxu rtw88 rtw88-pci rtw88-usb rtw88-sdio rtw88-8821c rtw88-8822b rtw88-8822c \ rtw88-8723d rtw88-8821ce rtw88-8821cu rtw88-8822be rtw88-8822bu \ rtw88-8822ce rtw88-8822cu rtw88-8723de rtw88-8723ds rtw88-8723du \ - rtw89 rtw89-pci rtw89-8851be rtw89-8852ae rtw89-8852be rtw89-8852ce + rtw89 rtw89-pci rtw89-8851be rtw89-8852ae rtw89-8852b-common \ + rtw89-8852be rtw89-8852ce config-$(call config_package,rtlwifi) += RTL_CARDS RTLWIFI config-$(call config_package,rtlwifi-pci) += RTLWIFI_PCI @@ -51,6 +52,7 @@ config-$(call config_package,rtw89) += RTW89 RTW89_CORE config-$(call config_package,rtw89-pci) += RTW89_PCI config-$(call config_package,rtw89-8851be) += RTW89_8851B RTW89_8851BE config-$(call config_package,rtw89-8852ae) += RTW89_8852A RTW89_8852AE +config-$(call config_package,rtw89-8852b-common) += RTW89_8852B_COMMON config-$(call config_package,rtw89-8852be) += RTW89_8852B RTW89_8852BE config-$(call config_package,rtw89-8852ce) += RTW89_8852C RTW89_8852CE config-$(CONFIG_PACKAGE_RTW89_DEBUG) += RTW89_DEBUG @@ -431,10 +433,20 @@ define KernelPackage/rtw89-8852ae AUTOLOAD:=$(call AutoProbe,rtw89_8852ae) endef +define KernelPackage/rtw89-8852b-common + $(call KernelPackage/mac80211/Default) + TITLE:=Realtek RTL8852B family support + DEPENDS+= +kmod-rtw89-pci + FILES:= \ + $(PKG_BUILD_DIR)/drivers/net/wireless/realtek/rtw89/rtw89_8852b_common.ko + AUTOLOAD:=$(call AutoProbe,rtw89_8852b_common) + HIDDEN:=1 +endef + define KernelPackage/rtw89-8852be $(call KernelPackage/mac80211/Default) TITLE:=Realtek RTL8852BE support - DEPENDS+= +kmod-rtw89-pci +rtl8852be-firmware + DEPENDS+= +kmod-rtw89-8852b-common +rtl8852be-firmware FILES:= \ $(PKG_BUILD_DIR)/drivers/net/wireless/realtek/rtw89/rtw89_8852b.ko \ $(PKG_BUILD_DIR)/drivers/net/wireless/realtek/rtw89/rtw89_8852be.ko diff --git a/lede/package/lean/default-settings/files/zzz-default-settings b/lede/package/lean/default-settings/files/zzz-default-settings index d4f6504217..7cea30987f 100755 --- a/lede/package/lean/default-settings/files/zzz-default-settings +++ b/lede/package/lean/default-settings/files/zzz-default-settings @@ -49,12 +49,12 @@ sed -i '/check_signature/d' /etc/opkg.conf sed -i '/REDIRECT --to-ports 53/d' /etc/firewall.user sed -i '/DISTRIB_REVISION/d' /etc/openwrt_release -echo "DISTRIB_REVISION='R25.10.10'" >> /etc/openwrt_release +echo "DISTRIB_REVISION='R26.02.20'" >> /etc/openwrt_release sed -i '/DISTRIB_DESCRIPTION/d' /etc/openwrt_release echo "DISTRIB_DESCRIPTION='LEDE '" >> /etc/openwrt_release sed -i '/OPENWRT_RELEASE/d' /usr/lib/os-release -echo 'OPENWRT_RELEASE="LEDE R25.10.10"' >> /usr/lib/os-release +echo 'OPENWRT_RELEASE="LEDE R26.02.20"' >> /usr/lib/os-release sed -i '/log-facility/d' /etc/dnsmasq.conf echo "log-facility=/dev/null" >> /etc/dnsmasq.conf diff --git a/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt b/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt index 7f8b50fa76..a3f036acf0 100644 --- a/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt +++ b/sing-box/clients/android/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt @@ -142,10 +142,10 @@ class BoxService(private val service: Service, private val platformInterface: Pl val appList = Settings.getEffectivePerAppProxyList() if (Settings.getEffectivePerAppProxyMode() == Settings.PER_APP_PROXY_INCLUDE) { includePackage = - PlatformInterfaceWrapper.StringArray(appList.iterator()) + PlatformInterfaceWrapper.StringArray((appList + Application.application.packageName).iterator()) } else { excludePackage = - PlatformInterfaceWrapper.StringArray(appList.iterator()) + PlatformInterfaceWrapper.StringArray((appList - Application.application.packageName).iterator()) } } }, @@ -224,9 +224,9 @@ class BoxService(private val service: Service, private val platformInterface: Pl if (Vendor.isPerAppProxyAvailable() && Settings.perAppProxyEnabled) { val appList = Settings.getEffectivePerAppProxyList() if (Settings.getEffectivePerAppProxyMode() == Settings.PER_APP_PROXY_INCLUDE) { - includePackage = PlatformInterfaceWrapper.StringArray(appList.iterator()) + includePackage = PlatformInterfaceWrapper.StringArray((appList + Application.application.packageName).iterator()) } else { - excludePackage = PlatformInterfaceWrapper.StringArray(appList.iterator()) + excludePackage = PlatformInterfaceWrapper.StringArray((appList - Application.application.packageName).iterator()) } } }, diff --git a/sing-box/clients/android/version.properties b/sing-box/clients/android/version.properties index cd9c953382..28ee84d52a 100644 --- a/sing-box/clients/android/version.properties +++ b/sing-box/clients/android/version.properties @@ -1,5 +1,5 @@ -VERSION_CODE=623 -VERSION_NAME=1.13.0-rc.6 +VERSION_CODE=624 +VERSION_NAME=1.13.0-rc.7 GO_VERSION=go1.25.7 diff --git a/sing-box/clients/apple/MacLibrary/CodeEditTextView.swift b/sing-box/clients/apple/MacLibrary/CodeEditTextView.swift index 04240f479c..dcfcbde34a 100644 --- a/sing-box/clients/apple/MacLibrary/CodeEditTextView.swift +++ b/sing-box/clients/apple/MacLibrary/CodeEditTextView.swift @@ -128,6 +128,9 @@ struct CodeEditTextView: NSViewRepresentable { guard let controller = context.coordinator.controller else { return } if controller.text != text { controller.text = text + // setText() creates a new Highlighter but doesn't trigger initial highlighting. + // Re-setting the language forces the highlighter to invalidate and re-highlight. + controller.language = .json } if controller.configuration.behavior.isEditable != isEditable { controller.configuration = makeConfiguration(isEditable: isEditable) diff --git a/sing-box/docs/changelog.md b/sing-box/docs/changelog.md index d316282435..72776c8972 100644 --- a/sing-box/docs/changelog.md +++ b/sing-box/docs/changelog.md @@ -2,9 +2,9 @@ icon: material/alert-decagram --- -#### 1.13.0-rc.6 +#### 1.13.0-rc.7 -* Fixes and improvements +* Add advertise tags support for Tailscale endpoint Important changes since 1.12: @@ -136,6 +136,7 @@ See [Dial Fields](/configuration/shared/dial/#bind_address_no_port). Tailscale endpoint can now create a system TUN interface to handle traffic directly. New `relay_server_port` and `relay_server_static_endpoints` options for incoming relay connections. +New `advertise_tags` option for ACL tag advertisement. See [Tailscale endpoint](/configuration/endpoint/tailscale/). diff --git a/sing-box/docs/configuration/endpoint/tailscale.md b/sing-box/docs/configuration/endpoint/tailscale.md index 5ea89a0ecd..6cf10e2ba9 100644 --- a/sing-box/docs/configuration/endpoint/tailscale.md +++ b/sing-box/docs/configuration/endpoint/tailscale.md @@ -8,7 +8,8 @@ icon: material/new-box :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) :material-plus: [system_interface](#system_interface) :material-plus: [system_interface_name](#system_interface_name) - :material-plus: [system_interface_mtu](#system_interface_mtu) + :material-plus: [system_interface_mtu](#system_interface_mtu) + :material-plus: [advertise_tags](#advertise_tags) !!! question "Since sing-box 1.12.0" @@ -28,6 +29,7 @@ icon: material/new-box "exit_node_allow_lan_access": false, "advertise_routes": [], "advertise_exit_node": false, + "advertise_tags": [], "relay_server_port": 0, "relay_server_static_endpoints": [], "system_interface": false, @@ -102,6 +104,14 @@ Example: `["192.168.1.1/24"]` Indicates whether the node should advertise itself as an exit node. +#### advertise_tags + +!!! question "Since sing-box 1.13.0" + +Tags to advertise for this node, for ACL enforcement purposes. + +Example: `["tag:server"]` + #### relay_server_port !!! question "Since sing-box 1.13.0" diff --git a/sing-box/docs/configuration/endpoint/tailscale.zh.md b/sing-box/docs/configuration/endpoint/tailscale.zh.md index 1bd658784d..f881dd67f2 100644 --- a/sing-box/docs/configuration/endpoint/tailscale.zh.md +++ b/sing-box/docs/configuration/endpoint/tailscale.zh.md @@ -8,7 +8,8 @@ icon: material/new-box :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) :material-plus: [system_interface](#system_interface) :material-plus: [system_interface_name](#system_interface_name) - :material-plus: [system_interface_mtu](#system_interface_mtu) + :material-plus: [system_interface_mtu](#system_interface_mtu) + :material-plus: [advertise_tags](#advertise_tags) !!! question "自 sing-box 1.12.0 起" @@ -28,6 +29,7 @@ icon: material/new-box "exit_node_allow_lan_access": false, "advertise_routes": [], "advertise_exit_node": false, + "advertise_tags": [], "relay_server_port": 0, "relay_server_static_endpoints": [], "system_interface": false, @@ -101,6 +103,14 @@ icon: material/new-box 指示节点是否应将自己通告为出口节点。 +#### advertise_tags + +!!! question "自 sing-box 1.13.0 起" + +为此节点通告的标签,用于 ACL 执行。 + +示例:`["tag:server"]` + #### relay_server_port !!! question "自 sing-box 1.13.0 起" diff --git a/sing-box/go.mod b/sing-box/go.mod index ad9d4f0690..0523b73fc9 100644 --- a/sing-box/go.mod +++ b/sing-box/go.mod @@ -3,7 +3,7 @@ module github.com/sagernet/sing-box go 1.24.7 require ( - github.com/anthropics/anthropic-sdk-go v1.19.0 + github.com/anthropics/anthropic-sdk-go v1.26.0 github.com/anytls/sing-anytls v0.0.11 github.com/caddyserver/certmagic v0.25.0 github.com/coder/websocket v1.8.14 @@ -22,7 +22,7 @@ require ( github.com/metacubex/utls v1.8.4 github.com/mholt/acmez/v3 v3.1.4 github.com/miekg/dns v1.1.69 - github.com/openai/openai-go/v3 v3.15.0 + github.com/openai/openai-go/v3 v3.23.0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a @@ -43,7 +43,7 @@ require ( github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 - github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 + github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 diff --git a/sing-box/go.sum b/sing-box/go.sum index 7d952142ce..738a415b1d 100644 --- a/sing-box/go.sum +++ b/sing-box/go.sum @@ -8,8 +8,8 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/anthropics/anthropic-sdk-go v1.19.0 h1:mO6E+ffSzLRvR/YUH9KJC0uGw0uV8GjISIuzem//3KE= -github.com/anthropics/anthropic-sdk-go v1.19.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= +github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY= +github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q= github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc= github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic= @@ -38,6 +38,8 @@ github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbww github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE= @@ -126,8 +128,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/openai/openai-go/v3 v3.15.0 h1:hk99rM7YPz+M99/5B/zOQcVwFRLLMdprVGx1vaZ8XMo= -github.com/openai/openai-go/v3 v3.15.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= +github.com/openai/openai-go/v3 v3.23.0 h1:FRFwTcB4FoWFtIunTY/8fgHvzSHgqbfWjiCwOMVrsvw= +github.com/openai/openai-go/v3 v3.23.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= @@ -236,8 +238,6 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.17.0.20260223095246-5715a3919a7f h1:3B8auqVameLvPhWxzw5S8QWdLTv19o0M8LYxPOXNm0g= -github.com/sagernet/sing-tun v0.8.0-beta.17.0.20260223095246-5715a3919a7f/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-tun v0.8.0-beta.18 h1:C6oHxP9BNBVEVdC9ABMTXmKej9mUVtcuw2v+IiBS8yw= github.com/sagernet/sing-tun v0.8.0-beta.18/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= @@ -246,8 +246,8 @@ github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1h github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 h1:eYz/OpMqWCvO2++iw3dEuzrlfC2xv78GdlGvprIM6O8= github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc= -github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA= -github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= +github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c h1:f9cXNB+IOOPnR8DOLMTpr42jf7naxh5Un5Y09BBf5Cg= +github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= @@ -380,6 +380,8 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sing-box/option/tailscale.go b/sing-box/option/tailscale.go index 661d91a354..dac8e866a5 100644 --- a/sing-box/option/tailscale.go +++ b/sing-box/option/tailscale.go @@ -12,22 +12,23 @@ import ( type TailscaleEndpointOptions struct { DialerOptions - StateDirectory string `json:"state_directory,omitempty"` - AuthKey string `json:"auth_key,omitempty"` - ControlURL string `json:"control_url,omitempty"` - Ephemeral bool `json:"ephemeral,omitempty"` - Hostname string `json:"hostname,omitempty"` - AcceptRoutes bool `json:"accept_routes,omitempty"` - ExitNode string `json:"exit_node,omitempty"` - ExitNodeAllowLANAccess bool `json:"exit_node_allow_lan_access,omitempty"` - AdvertiseRoutes []netip.Prefix `json:"advertise_routes,omitempty"` - AdvertiseExitNode bool `json:"advertise_exit_node,omitempty"` - RelayServerPort *uint16 `json:"relay_server_port,omitempty"` - RelayServerStaticEndpoints []netip.AddrPort `json:"relay_server_static_endpoints,omitempty"` - SystemInterface bool `json:"system_interface,omitempty"` - SystemInterfaceName string `json:"system_interface_name,omitempty"` - SystemInterfaceMTU uint32 `json:"system_interface_mtu,omitempty"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + StateDirectory string `json:"state_directory,omitempty"` + AuthKey string `json:"auth_key,omitempty"` + ControlURL string `json:"control_url,omitempty"` + Ephemeral bool `json:"ephemeral,omitempty"` + Hostname string `json:"hostname,omitempty"` + AcceptRoutes bool `json:"accept_routes,omitempty"` + ExitNode string `json:"exit_node,omitempty"` + ExitNodeAllowLANAccess bool `json:"exit_node_allow_lan_access,omitempty"` + AdvertiseRoutes []netip.Prefix `json:"advertise_routes,omitempty"` + AdvertiseExitNode bool `json:"advertise_exit_node,omitempty"` + AdvertiseTags badoption.Listable[string] `json:"advertise_tags,omitempty"` + RelayServerPort *uint16 `json:"relay_server_port,omitempty"` + RelayServerStaticEndpoints []netip.AddrPort `json:"relay_server_static_endpoints,omitempty"` + SystemInterface bool `json:"system_interface,omitempty"` + SystemInterfaceName string `json:"system_interface_name,omitempty"` + SystemInterfaceMTU uint32 `json:"system_interface_mtu,omitempty"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` } type TailscaleDNSServerOptions struct { diff --git a/sing-box/protocol/tailscale/endpoint.go b/sing-box/protocol/tailscale/endpoint.go index 1bd63e7159..40bc4bc648 100644 --- a/sing-box/protocol/tailscale/endpoint.go +++ b/sing-box/protocol/tailscale/endpoint.go @@ -97,6 +97,7 @@ type Endpoint struct { exitNodeAllowLANAccess bool advertiseRoutes []netip.Prefix advertiseExitNode bool + advertiseTags []string relayServerPort *uint16 relayServerStaticEndpoints []netip.AddrPort @@ -244,6 +245,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL exitNodeAllowLANAccess: options.ExitNodeAllowLANAccess, advertiseRoutes: options.AdvertiseRoutes, advertiseExitNode: options.AdvertiseExitNode, + advertiseTags: options.AdvertiseTags, relayServerPort: options.RelayServerPort, relayServerStaticEndpoints: options.RelayServerStaticEndpoints, udpTimeout: udpTimeout, @@ -359,25 +361,25 @@ func (t *Endpoint) Start(stage adapter.StartStage) error { localBackend := t.server.ExportLocalBackend() perfs := &ipn.MaskedPrefs{ Prefs: ipn.Prefs{ - RouteAll: t.acceptRoutes, + RouteAll: t.acceptRoutes, + AdvertiseRoutes: t.advertiseRoutes, + AdvertiseTags: t.advertiseTags, }, - RouteAllSet: true, - ExitNodeIPSet: true, - AdvertiseRoutesSet: true, - } - if len(t.advertiseRoutes) > 0 { - perfs.AdvertiseRoutes = t.advertiseRoutes + RouteAllSet: true, + ExitNodeIPSet: true, + AdvertiseRoutesSet: true, + AdvertiseTagsSet: true, + RelayServerPortSet: true, + RelayServerStaticEndpointsSet: true, } if t.advertiseExitNode { perfs.AdvertiseRoutes = append(perfs.AdvertiseRoutes, tsaddr.ExitRoutes()...) } if t.relayServerPort != nil { perfs.RelayServerPort = t.relayServerPort - perfs.RelayServerPortSet = true } if len(t.relayServerStaticEndpoints) > 0 { perfs.RelayServerStaticEndpoints = t.relayServerStaticEndpoints - perfs.RelayServerStaticEndpointsSet = true } _, err = localBackend.EditPrefs(perfs) if err != nil { diff --git a/sing-box/service/ccm/service.go b/sing-box/service/ccm/service.go index 94e47734c4..078ed9418f 100644 --- a/sing-box/service/ccm/service.go +++ b/sing-box/service/ccm/service.go @@ -425,6 +425,8 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons usage.OutputTokens, usage.CacheReadInputTokens, usage.CacheCreationInputTokens, + usage.CacheCreation.Ephemeral5mInputTokens, + usage.CacheCreation.Ephemeral1hInputTokens, username, ) } @@ -485,6 +487,8 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons accumulatedUsage.InputTokens = messageStart.Message.Usage.InputTokens accumulatedUsage.CacheReadInputTokens = messageStart.Message.Usage.CacheReadInputTokens accumulatedUsage.CacheCreationInputTokens = messageStart.Message.Usage.CacheCreationInputTokens + accumulatedUsage.CacheCreation.Ephemeral5mInputTokens = messageStart.Message.Usage.CacheCreation.Ephemeral5mInputTokens + accumulatedUsage.CacheCreation.Ephemeral1hInputTokens = messageStart.Message.Usage.CacheCreation.Ephemeral1hInputTokens } case "message_delta": messageDelta := event.AsMessageDelta() @@ -519,6 +523,8 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons accumulatedUsage.OutputTokens, accumulatedUsage.CacheReadInputTokens, accumulatedUsage.CacheCreationInputTokens, + accumulatedUsage.CacheCreation.Ephemeral5mInputTokens, + accumulatedUsage.CacheCreation.Ephemeral1hInputTokens, username, ) } diff --git a/sing-box/service/ccm/service_usage.go b/sing-box/service/ccm/service_usage.go index 7d39e3ce56..383788d9ac 100644 --- a/sing-box/service/ccm/service_usage.go +++ b/sing-box/service/ccm/service_usage.go @@ -13,12 +13,14 @@ import ( ) type UsageStats struct { - RequestCount int `json:"request_count"` - MessagesCount int `json:"messages_count"` - InputTokens int64 `json:"input_tokens"` - OutputTokens int64 `json:"output_tokens"` - CacheReadInputTokens int64 `json:"cache_read_input_tokens"` - CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` + RequestCount int `json:"request_count"` + MessagesCount int `json:"messages_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CacheReadInputTokens int64 `json:"cache_read_input_tokens"` + CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` + CacheCreation5MinuteInputTokens int64 `json:"cache_creation_5m_input_tokens,omitempty"` + CacheCreation1HourInputTokens int64 `json:"cache_creation_1h_input_tokens,omitempty"` } type CostCombination struct { @@ -41,13 +43,15 @@ type AggregatedUsage struct { } type UsageStatsJSON struct { - RequestCount int `json:"request_count"` - MessagesCount int `json:"messages_count"` - InputTokens int64 `json:"input_tokens"` - OutputTokens int64 `json:"output_tokens"` - CacheReadInputTokens int64 `json:"cache_read_input_tokens"` - CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` - CostUSD float64 `json:"cost_usd"` + RequestCount int `json:"request_count"` + MessagesCount int `json:"messages_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CacheReadInputTokens int64 `json:"cache_read_input_tokens"` + CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` + CacheCreation5MinuteInputTokens int64 `json:"cache_creation_5m_input_tokens,omitempty"` + CacheCreation1HourInputTokens int64 `json:"cache_creation_1h_input_tokens,omitempty"` + CostUSD float64 `json:"cost_usd"` } type CostCombinationJSON struct { @@ -69,10 +73,11 @@ type AggregatedUsageJSON struct { } type ModelPricing struct { - InputPrice float64 - OutputPrice float64 - CacheReadPrice float64 - CacheWritePrice float64 + InputPrice float64 + OutputPrice float64 + CacheReadPrice float64 + CacheWritePrice5Minute float64 + CacheWritePrice1Hour float64 } type modelFamily struct { @@ -82,143 +87,205 @@ type modelFamily struct { } var ( - opus4Pricing = ModelPricing{ - InputPrice: 15.0, - OutputPrice: 75.0, - CacheReadPrice: 1.5, - CacheWritePrice: 18.75, + opus46StandardPricing = ModelPricing{ + InputPrice: 5.0, + OutputPrice: 25.0, + CacheReadPrice: 0.5, + CacheWritePrice5Minute: 6.25, + CacheWritePrice1Hour: 10.0, } - sonnet4StandardPricing = ModelPricing{ - InputPrice: 3.0, - OutputPrice: 15.0, - CacheReadPrice: 0.3, - CacheWritePrice: 3.75, - } - - sonnet4PremiumPricing = ModelPricing{ - InputPrice: 6.0, - OutputPrice: 22.5, - CacheReadPrice: 0.6, - CacheWritePrice: 7.5, - } - - haiku4Pricing = ModelPricing{ - InputPrice: 1.0, - OutputPrice: 5.0, - CacheReadPrice: 0.1, - CacheWritePrice: 1.25, - } - - haiku35Pricing = ModelPricing{ - InputPrice: 0.8, - OutputPrice: 4.0, - CacheReadPrice: 0.08, - CacheWritePrice: 1.0, - } - - sonnet35Pricing = ModelPricing{ - InputPrice: 3.0, - OutputPrice: 15.0, - CacheReadPrice: 0.3, - CacheWritePrice: 3.75, + opus46PremiumPricing = ModelPricing{ + InputPrice: 10.0, + OutputPrice: 37.5, + CacheReadPrice: 1.0, + CacheWritePrice5Minute: 12.5, + CacheWritePrice1Hour: 20.0, } opus45Pricing = ModelPricing{ - InputPrice: 5.0, - OutputPrice: 25.0, - CacheReadPrice: 0.5, - CacheWritePrice: 6.25, + InputPrice: 5.0, + OutputPrice: 25.0, + CacheReadPrice: 0.5, + CacheWritePrice5Minute: 6.25, + CacheWritePrice1Hour: 10.0, + } + + opus4Pricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 75.0, + CacheReadPrice: 1.5, + CacheWritePrice5Minute: 18.75, + CacheWritePrice1Hour: 30.0, + } + + sonnet46StandardPricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, + } + + sonnet46PremiumPricing = ModelPricing{ + InputPrice: 6.0, + OutputPrice: 22.5, + CacheReadPrice: 0.6, + CacheWritePrice5Minute: 7.5, + CacheWritePrice1Hour: 12.0, } sonnet45StandardPricing = ModelPricing{ - InputPrice: 3.0, - OutputPrice: 15.0, - CacheReadPrice: 0.3, - CacheWritePrice: 3.75, + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, } sonnet45PremiumPricing = ModelPricing{ - InputPrice: 6.0, - OutputPrice: 22.5, - CacheReadPrice: 0.6, - CacheWritePrice: 7.5, + InputPrice: 6.0, + OutputPrice: 22.5, + CacheReadPrice: 0.6, + CacheWritePrice5Minute: 7.5, + CacheWritePrice1Hour: 12.0, + } + + sonnet4StandardPricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, + } + + sonnet4PremiumPricing = ModelPricing{ + InputPrice: 6.0, + OutputPrice: 22.5, + CacheReadPrice: 0.6, + CacheWritePrice5Minute: 7.5, + CacheWritePrice1Hour: 12.0, + } + + sonnet37Pricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, + } + + sonnet35Pricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, } haiku45Pricing = ModelPricing{ - InputPrice: 1.0, - OutputPrice: 5.0, - CacheReadPrice: 0.1, - CacheWritePrice: 1.25, + InputPrice: 1.0, + OutputPrice: 5.0, + CacheReadPrice: 0.1, + CacheWritePrice5Minute: 1.25, + CacheWritePrice1Hour: 2.0, + } + + haiku4Pricing = ModelPricing{ + InputPrice: 1.0, + OutputPrice: 5.0, + CacheReadPrice: 0.1, + CacheWritePrice5Minute: 1.25, + CacheWritePrice1Hour: 2.0, + } + + haiku35Pricing = ModelPricing{ + InputPrice: 0.8, + OutputPrice: 4.0, + CacheReadPrice: 0.08, + CacheWritePrice5Minute: 1.0, + CacheWritePrice1Hour: 1.6, } haiku3Pricing = ModelPricing{ - InputPrice: 0.25, - OutputPrice: 1.25, - CacheReadPrice: 0.03, - CacheWritePrice: 0.3, + InputPrice: 0.25, + OutputPrice: 1.25, + CacheReadPrice: 0.03, + CacheWritePrice5Minute: 0.3, + CacheWritePrice1Hour: 0.5, } opus3Pricing = ModelPricing{ - InputPrice: 15.0, - OutputPrice: 75.0, - CacheReadPrice: 1.5, - CacheWritePrice: 18.75, + InputPrice: 15.0, + OutputPrice: 75.0, + CacheReadPrice: 1.5, + CacheWritePrice5Minute: 18.75, + CacheWritePrice1Hour: 30.0, } modelFamilies = []modelFamily{ { - pattern: regexp.MustCompile(`^claude-opus-4-5-`), + pattern: regexp.MustCompile(`^claude-opus-4-6(?:-|$)`), + standardPricing: opus46StandardPricing, + premiumPricing: &opus46PremiumPricing, + }, + { + pattern: regexp.MustCompile(`^claude-opus-4-5(?:-|$)`), standardPricing: opus45Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-(?:opus-4-|4-opus-|opus-4-1-)`), + pattern: regexp.MustCompile(`^claude-(?:opus-4(?:-|$)|4-opus-)`), standardPricing: opus4Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-(?:opus-3-|3-opus-)`), + pattern: regexp.MustCompile(`^claude-(?:opus-3(?:-|$)|3-opus-)`), standardPricing: opus3Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-(?:sonnet-4-5-|4-5-sonnet-)`), + pattern: regexp.MustCompile(`^claude-(?:sonnet-4-6(?:-|$)|4-6-sonnet-)`), + standardPricing: sonnet46StandardPricing, + premiumPricing: &sonnet46PremiumPricing, + }, + { + pattern: regexp.MustCompile(`^claude-(?:sonnet-4-5(?:-|$)|4-5-sonnet-)`), standardPricing: sonnet45StandardPricing, premiumPricing: &sonnet45PremiumPricing, }, { - pattern: regexp.MustCompile(`^claude-3-7-sonnet-`), + pattern: regexp.MustCompile(`^claude-(?:sonnet-4(?:-|$)|4-sonnet-)`), standardPricing: sonnet4StandardPricing, premiumPricing: &sonnet4PremiumPricing, }, { - pattern: regexp.MustCompile(`^claude-(?:sonnet-4-|4-sonnet-)`), - standardPricing: sonnet4StandardPricing, - premiumPricing: &sonnet4PremiumPricing, + pattern: regexp.MustCompile(`^claude-3-7-sonnet(?:-|$)`), + standardPricing: sonnet37Pricing, + premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-3-5-sonnet-`), + pattern: regexp.MustCompile(`^claude-3-5-sonnet(?:-|$)`), standardPricing: sonnet35Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-(?:haiku-4-5-|4-5-haiku-)`), + pattern: regexp.MustCompile(`^claude-(?:haiku-4-5(?:-|$)|4-5-haiku-)`), standardPricing: haiku45Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-haiku-4-`), + pattern: regexp.MustCompile(`^claude-haiku-4(?:-|$)`), standardPricing: haiku4Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-3-5-haiku-`), + pattern: regexp.MustCompile(`^claude-3-5-haiku(?:-|$)`), standardPricing: haiku35Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-3-haiku-`), + pattern: regexp.MustCompile(`^claude-3-haiku(?:-|$)`), standardPricing: haiku3Pricing, premiumPricing: nil, }, @@ -243,10 +310,20 @@ func getPricing(model string, contextWindow int) ModelPricing { func calculateCost(stats UsageStats, model string, contextWindow int) float64 { pricing := getPricing(model, contextWindow) + cacheCreationCost := 0.0 + if stats.CacheCreation5MinuteInputTokens > 0 || stats.CacheCreation1HourInputTokens > 0 { + cacheCreationCost = + float64(stats.CacheCreation5MinuteInputTokens)*pricing.CacheWritePrice5Minute + + float64(stats.CacheCreation1HourInputTokens)*pricing.CacheWritePrice1Hour + } else { + // Backward compatibility for usage files generated before TTL split tracking. + cacheCreationCost = float64(stats.CacheCreationInputTokens) * pricing.CacheWritePrice5Minute + } + cost := (float64(stats.InputTokens)*pricing.InputPrice + float64(stats.OutputTokens)*pricing.OutputPrice + float64(stats.CacheReadInputTokens)*pricing.CacheReadPrice + - float64(stats.CacheCreationInputTokens)*pricing.CacheWritePrice) / 1_000_000 + cacheCreationCost) / 1_000_000 return math.Round(cost*100) / 100 } @@ -273,13 +350,15 @@ func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON { Model: combo.Model, ContextWindow: combo.ContextWindow, Total: UsageStatsJSON{ - RequestCount: combo.Total.RequestCount, - MessagesCount: combo.Total.MessagesCount, - InputTokens: combo.Total.InputTokens, - OutputTokens: combo.Total.OutputTokens, - CacheReadInputTokens: combo.Total.CacheReadInputTokens, - CacheCreationInputTokens: combo.Total.CacheCreationInputTokens, - CostUSD: totalCost, + RequestCount: combo.Total.RequestCount, + MessagesCount: combo.Total.MessagesCount, + InputTokens: combo.Total.InputTokens, + OutputTokens: combo.Total.OutputTokens, + CacheReadInputTokens: combo.Total.CacheReadInputTokens, + CacheCreationInputTokens: combo.Total.CacheCreationInputTokens, + CacheCreation5MinuteInputTokens: combo.Total.CacheCreation5MinuteInputTokens, + CacheCreation1HourInputTokens: combo.Total.CacheCreation1HourInputTokens, + CostUSD: totalCost, }, ByUser: make(map[string]UsageStatsJSON), } @@ -289,13 +368,15 @@ func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON { result.Costs.ByUser[user] += userCost comboJSON.ByUser[user] = UsageStatsJSON{ - RequestCount: userStats.RequestCount, - MessagesCount: userStats.MessagesCount, - InputTokens: userStats.InputTokens, - OutputTokens: userStats.OutputTokens, - CacheReadInputTokens: userStats.CacheReadInputTokens, - CacheCreationInputTokens: userStats.CacheCreationInputTokens, - CostUSD: userCost, + RequestCount: userStats.RequestCount, + MessagesCount: userStats.MessagesCount, + InputTokens: userStats.InputTokens, + OutputTokens: userStats.OutputTokens, + CacheReadInputTokens: userStats.CacheReadInputTokens, + CacheCreationInputTokens: userStats.CacheCreationInputTokens, + CacheCreation5MinuteInputTokens: userStats.CacheCreation5MinuteInputTokens, + CacheCreation1HourInputTokens: userStats.CacheCreation1HourInputTokens, + CostUSD: userCost, } } @@ -367,7 +448,13 @@ func (u *AggregatedUsage) Save() error { return err } -func (u *AggregatedUsage) AddUsage(model string, contextWindow int, messagesCount int, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens int64, user string) error { +func (u *AggregatedUsage) AddUsage( + model string, + contextWindow int, + messagesCount int, + inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64, + user string, +) error { if model == "" { return E.New("model cannot be empty") } @@ -400,6 +487,10 @@ func (u *AggregatedUsage) AddUsage(model string, contextWindow int, messagesCoun combo = &u.Combinations[len(u.Combinations)-1] } + if cacheCreationTokens == 0 { + cacheCreationTokens = cacheCreation5MinuteTokens + cacheCreation1HourTokens + } + // Update total stats combo.Total.RequestCount++ combo.Total.MessagesCount += messagesCount @@ -407,6 +498,8 @@ func (u *AggregatedUsage) AddUsage(model string, contextWindow int, messagesCoun combo.Total.OutputTokens += outputTokens combo.Total.CacheReadInputTokens += cacheReadTokens combo.Total.CacheCreationInputTokens += cacheCreationTokens + combo.Total.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens + combo.Total.CacheCreation1HourInputTokens += cacheCreation1HourTokens // Update per-user stats if user is specified if user != "" { @@ -417,6 +510,8 @@ func (u *AggregatedUsage) AddUsage(model string, contextWindow int, messagesCoun userStats.OutputTokens += outputTokens userStats.CacheReadInputTokens += cacheReadTokens userStats.CacheCreationInputTokens += cacheCreationTokens + userStats.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens + userStats.CacheCreation1HourInputTokens += cacheCreation1HourTokens combo.ByUser[user] = userStats } diff --git a/sing-box/service/ocm/service.go b/sing-box/service/ocm/service.go index fc655f673e..3efb24f12d 100644 --- a/sing-box/service/ocm/service.go +++ b/sing-box/service/ocm/service.go @@ -406,7 +406,9 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons isChatCompletions := path == "/v1/chat/completions" mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type")) isStreaming := err == nil && mediaType == "text/event-stream" - + if !isStreaming && !isChatCompletions && response.Header.Get("Content-Type") == "" { + isStreaming = true + } if !isStreaming { bodyBytes, err := io.ReadAll(response.Body) if err != nil { @@ -414,13 +416,14 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons return } - var responseModel string + var responseModel, serviceTier string var inputTokens, outputTokens, cachedTokens int64 if isChatCompletions { var chatCompletion openai.ChatCompletion if json.Unmarshal(bodyBytes, &chatCompletion) == nil { responseModel = chatCompletion.Model + serviceTier = string(chatCompletion.ServiceTier) inputTokens = chatCompletion.Usage.PromptTokens outputTokens = chatCompletion.Usage.CompletionTokens cachedTokens = chatCompletion.Usage.PromptTokensDetails.CachedTokens @@ -429,6 +432,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons var responsesResponse responses.Response if json.Unmarshal(bodyBytes, &responsesResponse) == nil { responseModel = string(responsesResponse.Model) + serviceTier = string(responsesResponse.ServiceTier) inputTokens = responsesResponse.Usage.InputTokens outputTokens = responsesResponse.Usage.OutputTokens cachedTokens = responsesResponse.Usage.InputTokensDetails.CachedTokens @@ -440,7 +444,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons responseModel = requestModel } if responseModel != "" { - s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, username) + s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, serviceTier, username) } } @@ -455,7 +459,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons } var inputTokens, outputTokens, cachedTokens int64 - var responseModel string + var responseModel, serviceTier string buffer := make([]byte, buf.BufferSize) var leftover []byte @@ -490,6 +494,9 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons if chatChunk.Model != "" { responseModel = chatChunk.Model } + if chatChunk.ServiceTier != "" { + serviceTier = string(chatChunk.ServiceTier) + } if chatChunk.Usage.PromptTokens > 0 { inputTokens = chatChunk.Usage.PromptTokens cachedTokens = chatChunk.Usage.PromptTokensDetails.CachedTokens @@ -506,6 +513,9 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons if string(completedEvent.Response.Model) != "" { responseModel = string(completedEvent.Response.Model) } + if completedEvent.Response.ServiceTier != "" { + serviceTier = string(completedEvent.Response.ServiceTier) + } if completedEvent.Response.Usage.InputTokens > 0 { inputTokens = completedEvent.Response.Usage.InputTokens cachedTokens = completedEvent.Response.Usage.InputTokensDetails.CachedTokens @@ -534,7 +544,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons if inputTokens > 0 || outputTokens > 0 { if responseModel != "" { - s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, username) + s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, serviceTier, username) } } return diff --git a/sing-box/service/ocm/service_usage.go b/sing-box/service/ocm/service_usage.go index 7089f4d391..36ec44b734 100644 --- a/sing-box/service/ocm/service_usage.go +++ b/sing-box/service/ocm/service_usage.go @@ -5,6 +5,7 @@ import ( "math" "os" "regexp" + "strings" "sync" "time" @@ -42,9 +43,10 @@ func (u *UsageStats) UnmarshalJSON(data []byte) error { } type CostCombination struct { - Model string `json:"model"` - Total UsageStats `json:"total"` - ByUser map[string]UsageStats `json:"by_user"` + Model string `json:"model"` + ServiceTier string `json:"service_tier,omitempty"` + Total UsageStats `json:"total"` + ByUser map[string]UsageStats `json:"by_user"` } type AggregatedUsage struct { @@ -68,9 +70,10 @@ type UsageStatsJSON struct { } type CostCombinationJSON struct { - Model string `json:"model"` - Total UsageStatsJSON `json:"total"` - ByUser map[string]UsageStatsJSON `json:"by_user"` + Model string `json:"model"` + ServiceTier string `json:"service_tier,omitempty"` + Total UsageStatsJSON `json:"total"` + ByUser map[string]UsageStatsJSON `json:"by_user"` } type CostsSummaryJSON struct { @@ -95,7 +98,123 @@ type modelFamily struct { pricing ModelPricing } +const ( + serviceTierAuto = "auto" + serviceTierDefault = "default" + serviceTierFlex = "flex" + serviceTierPriority = "priority" + serviceTierScale = "scale" +) + var ( + gpt52Pricing = ModelPricing{ + InputPrice: 1.75, + OutputPrice: 14.0, + CachedInputPrice: 0.175, + } + + gpt5Pricing = ModelPricing{ + InputPrice: 1.25, + OutputPrice: 10.0, + CachedInputPrice: 0.125, + } + + gpt5MiniPricing = ModelPricing{ + InputPrice: 0.25, + OutputPrice: 2.0, + CachedInputPrice: 0.025, + } + + gpt5NanoPricing = ModelPricing{ + InputPrice: 0.05, + OutputPrice: 0.4, + CachedInputPrice: 0.005, + } + + gpt52CodexPricing = ModelPricing{ + InputPrice: 1.75, + OutputPrice: 14.0, + CachedInputPrice: 0.175, + } + + gpt51CodexPricing = ModelPricing{ + InputPrice: 1.25, + OutputPrice: 10.0, + CachedInputPrice: 0.125, + } + + gpt51CodexMiniPricing = ModelPricing{ + InputPrice: 0.25, + OutputPrice: 2.0, + CachedInputPrice: 0.025, + } + + gpt52ProPricing = ModelPricing{ + InputPrice: 21.0, + OutputPrice: 168.0, + CachedInputPrice: 21.0, + } + + gpt5ProPricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 120.0, + CachedInputPrice: 15.0, + } + + gpt52FlexPricing = ModelPricing{ + InputPrice: 0.875, + OutputPrice: 7.0, + CachedInputPrice: 0.0875, + } + + gpt5FlexPricing = ModelPricing{ + InputPrice: 0.625, + OutputPrice: 5.0, + CachedInputPrice: 0.0625, + } + + gpt5MiniFlexPricing = ModelPricing{ + InputPrice: 0.125, + OutputPrice: 1.0, + CachedInputPrice: 0.0125, + } + + gpt5NanoFlexPricing = ModelPricing{ + InputPrice: 0.025, + OutputPrice: 0.2, + CachedInputPrice: 0.0025, + } + + gpt52PriorityPricing = ModelPricing{ + InputPrice: 3.5, + OutputPrice: 28.0, + CachedInputPrice: 0.35, + } + + gpt5PriorityPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 20.0, + CachedInputPrice: 0.25, + } + + gpt5MiniPriorityPricing = ModelPricing{ + InputPrice: 0.45, + OutputPrice: 3.6, + CachedInputPrice: 0.045, + } + + gpt52CodexPriorityPricing = ModelPricing{ + InputPrice: 3.5, + OutputPrice: 28.0, + CachedInputPrice: 0.35, + } + + gpt51CodexPriorityPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 20.0, + CachedInputPrice: 0.25, + } + gpt4oPricing = ModelPricing{ InputPrice: 2.5, OutputPrice: 10.0, @@ -111,7 +230,19 @@ var ( gpt4oAudioPricing = ModelPricing{ InputPrice: 2.5, OutputPrice: 10.0, - CachedInputPrice: 1.25, + CachedInputPrice: 2.5, + } + + gpt4oMiniAudioPricing = ModelPricing{ + InputPrice: 0.15, + OutputPrice: 0.6, + CachedInputPrice: 0.15, + } + + gptAudioMiniPricing = ModelPricing{ + InputPrice: 0.6, + OutputPrice: 2.4, + CachedInputPrice: 0.6, } o1Pricing = ModelPricing{ @@ -120,6 +251,12 @@ var ( CachedInputPrice: 7.5, } + o1ProPricing = ModelPricing{ + InputPrice: 150.0, + OutputPrice: 600.0, + CachedInputPrice: 150.0, + } + o1MiniPricing = ModelPricing{ InputPrice: 1.1, OutputPrice: 4.4, @@ -135,13 +272,55 @@ var ( o3Pricing = ModelPricing{ InputPrice: 2.0, OutputPrice: 8.0, - CachedInputPrice: 1.0, + CachedInputPrice: 0.5, + } + + o3ProPricing = ModelPricing{ + InputPrice: 20.0, + OutputPrice: 80.0, + CachedInputPrice: 20.0, + } + + o3DeepResearchPricing = ModelPricing{ + InputPrice: 10.0, + OutputPrice: 40.0, + CachedInputPrice: 2.5, } o4MiniPricing = ModelPricing{ InputPrice: 1.1, OutputPrice: 4.4, - CachedInputPrice: 0.55, + CachedInputPrice: 0.275, + } + + o4MiniDeepResearchPricing = ModelPricing{ + InputPrice: 2.0, + OutputPrice: 8.0, + CachedInputPrice: 0.5, + } + + o3FlexPricing = ModelPricing{ + InputPrice: 1.0, + OutputPrice: 4.0, + CachedInputPrice: 0.25, + } + + o4MiniFlexPricing = ModelPricing{ + InputPrice: 0.55, + OutputPrice: 2.2, + CachedInputPrice: 0.138, + } + + o3PriorityPricing = ModelPricing{ + InputPrice: 3.5, + OutputPrice: 14.0, + CachedInputPrice: 0.875, + } + + o4MiniPriorityPricing = ModelPricing{ + InputPrice: 2.0, + OutputPrice: 8.0, + CachedInputPrice: 0.5, } gpt41Pricing = ModelPricing{ @@ -162,69 +341,358 @@ var ( CachedInputPrice: 0.025, } - modelFamilies = []modelFamily{ + gpt41PriorityPricing = ModelPricing{ + InputPrice: 3.5, + OutputPrice: 14.0, + CachedInputPrice: 0.875, + } + + gpt41MiniPriorityPricing = ModelPricing{ + InputPrice: 0.7, + OutputPrice: 2.8, + CachedInputPrice: 0.175, + } + + gpt41NanoPriorityPricing = ModelPricing{ + InputPrice: 0.2, + OutputPrice: 0.8, + CachedInputPrice: 0.05, + } + + gpt4oPriorityPricing = ModelPricing{ + InputPrice: 4.25, + OutputPrice: 17.0, + CachedInputPrice: 2.125, + } + + gpt4oMiniPriorityPricing = ModelPricing{ + InputPrice: 0.25, + OutputPrice: 1.0, + CachedInputPrice: 0.125, + } + + standardModelFamilies = []modelFamily{ { - pattern: regexp.MustCompile(`^gpt-4\.1-nano`), - pricing: gpt41NanoPricing, + pattern: regexp.MustCompile(`^gpt-5\.2-codex(?:$|-)`), + pricing: gpt52CodexPricing, }, { - pattern: regexp.MustCompile(`^gpt-4\.1-mini`), - pricing: gpt41MiniPricing, + pattern: regexp.MustCompile(`^gpt-5\.1-codex-max(?:$|-)`), + pricing: gpt51CodexPricing, }, { - pattern: regexp.MustCompile(`^gpt-4\.1`), - pricing: gpt41Pricing, + pattern: regexp.MustCompile(`^gpt-5\.1-codex-mini(?:$|-)`), + pricing: gpt51CodexMiniPricing, }, { - pattern: regexp.MustCompile(`^o4-mini`), + pattern: regexp.MustCompile(`^gpt-5\.1-codex(?:$|-)`), + pricing: gpt51CodexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`), + pricing: gpt51CodexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2-chat-latest$`), + pricing: gpt52Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1-chat-latest$`), + pricing: gpt5Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-chat-latest$`), + pricing: gpt5Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2-pro(?:$|-)`), + pricing: gpt52ProPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-pro(?:$|-)`), + pricing: gpt5ProPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`), + pricing: gpt5MiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-nano(?:$|-)`), + pricing: gpt5NanoPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2(?:$|-)`), + pricing: gpt52Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1(?:$|-)`), + pricing: gpt5Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5(?:$|-)`), + pricing: gpt5Pricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini-deep-research(?:$|-)`), + pricing: o4MiniDeepResearchPricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini(?:$|-)`), pricing: o4MiniPricing, }, { - pattern: regexp.MustCompile(`^o3-mini`), + pattern: regexp.MustCompile(`^o3-pro(?:$|-)`), + pricing: o3ProPricing, + }, + { + pattern: regexp.MustCompile(`^o3-deep-research(?:$|-)`), + pricing: o3DeepResearchPricing, + }, + { + pattern: regexp.MustCompile(`^o3-mini(?:$|-)`), pricing: o3MiniPricing, }, { - pattern: regexp.MustCompile(`^o3`), + pattern: regexp.MustCompile(`^o3(?:$|-)`), pricing: o3Pricing, }, { - pattern: regexp.MustCompile(`^o1-mini`), + pattern: regexp.MustCompile(`^o1-pro(?:$|-)`), + pricing: o1ProPricing, + }, + { + pattern: regexp.MustCompile(`^o1-mini(?:$|-)`), pricing: o1MiniPricing, }, { - pattern: regexp.MustCompile(`^o1`), + pattern: regexp.MustCompile(`^o1(?:$|-)`), pricing: o1Pricing, }, { - pattern: regexp.MustCompile(`^gpt-4o-audio`), + pattern: regexp.MustCompile(`^gpt-4o-mini-audio(?:$|-)`), + pricing: gpt4oMiniAudioPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-audio-mini(?:$|-)`), + pricing: gptAudioMiniPricing, + }, + { + pattern: regexp.MustCompile(`^(?:gpt-4o-audio|gpt-audio)(?:$|-)`), pricing: gpt4oAudioPricing, }, { - pattern: regexp.MustCompile(`^gpt-4o-mini`), + pattern: regexp.MustCompile(`^gpt-4\.1-nano(?:$|-)`), + pricing: gpt41NanoPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1-mini(?:$|-)`), + pricing: gpt41MiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1(?:$|-)`), + pricing: gpt41Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o-mini(?:$|-)`), pricing: gpt4oMiniPricing, }, { - pattern: regexp.MustCompile(`^gpt-4o`), + pattern: regexp.MustCompile(`^gpt-4o(?:$|-)`), pricing: gpt4oPricing, }, { - pattern: regexp.MustCompile(`^chatgpt-4o`), + pattern: regexp.MustCompile(`^chatgpt-4o(?:$|-)`), pricing: gpt4oPricing, }, } + + flexModelFamilies = []modelFamily{ + { + pattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`), + pricing: gpt5MiniFlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-nano(?:$|-)`), + pricing: gpt5NanoFlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2(?:$|-)`), + pricing: gpt52FlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1(?:$|-)`), + pricing: gpt5FlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5(?:$|-)`), + pricing: gpt5FlexPricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini(?:$|-)`), + pricing: o4MiniFlexPricing, + }, + { + pattern: regexp.MustCompile(`^o3(?:$|-)`), + pricing: o3FlexPricing, + }, + } + + priorityModelFamilies = []modelFamily{ + { + pattern: regexp.MustCompile(`^gpt-5\.2-codex(?:$|-)`), + pricing: gpt52CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1-codex-max(?:$|-)`), + pricing: gpt51CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1-codex(?:$|-)`), + pricing: gpt51CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`), + pricing: gpt51CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`), + pricing: gpt5MiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2(?:$|-)`), + pricing: gpt52PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1(?:$|-)`), + pricing: gpt5PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5(?:$|-)`), + pricing: gpt5PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini(?:$|-)`), + pricing: o4MiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^o3(?:$|-)`), + pricing: o3PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1-nano(?:$|-)`), + pricing: gpt41NanoPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1-mini(?:$|-)`), + pricing: gpt41MiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1(?:$|-)`), + pricing: gpt41PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o-mini(?:$|-)`), + pricing: gpt4oMiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o(?:$|-)`), + pricing: gpt4oPriorityPricing, + }, + } ) -func getPricing(model string) ModelPricing { +func modelFamiliesForTier(serviceTier string) []modelFamily { + switch serviceTier { + case serviceTierFlex: + return flexModelFamilies + case serviceTierPriority: + return priorityModelFamilies + default: + return standardModelFamilies + } +} + +func findPricingInFamilies(model string, modelFamilies []modelFamily) (ModelPricing, bool) { for _, family := range modelFamilies { if family.pattern.MatchString(model) { - return family.pricing + return family.pricing, true } } + return ModelPricing{}, false +} + +func normalizeServiceTier(serviceTier string) string { + switch strings.ToLower(strings.TrimSpace(serviceTier)) { + case "", serviceTierAuto, serviceTierDefault: + return serviceTierDefault + case serviceTierFlex: + return serviceTierFlex + case serviceTierPriority: + return serviceTierPriority + case serviceTierScale: + // Scale-tier requests are prepaid differently and not listed in this usage file. + return serviceTierDefault + default: + return serviceTierDefault + } +} + +func getPricing(model string, serviceTier string) ModelPricing { + normalizedServiceTier := normalizeServiceTier(serviceTier) + modelFamilies := modelFamiliesForTier(normalizedServiceTier) + + if pricing, found := findPricingInFamilies(model, modelFamilies); found { + return pricing + } + + normalizedModel := normalizeGPT5Model(model) + if normalizedModel != model { + if pricing, found := findPricingInFamilies(normalizedModel, modelFamilies); found { + return pricing + } + } + + if normalizedServiceTier != serviceTierDefault { + if pricing, found := findPricingInFamilies(model, standardModelFamilies); found { + return pricing + } + if normalizedModel != model { + if pricing, found := findPricingInFamilies(normalizedModel, standardModelFamilies); found { + return pricing + } + } + } + return gpt4oPricing } -func calculateCost(stats UsageStats, model string) float64 { - pricing := getPricing(model) +func normalizeGPT5Model(model string) string { + if !strings.HasPrefix(model, "gpt-5.") { + return model + } + + switch { + case strings.Contains(model, "-codex-mini"): + return "gpt-5.1-codex-mini" + case strings.Contains(model, "-codex-max"): + return "gpt-5.1-codex-max" + case strings.Contains(model, "-codex"): + return "gpt-5.2-codex" + case strings.Contains(model, "-chat-latest"): + return "gpt-5.2-chat-latest" + case strings.Contains(model, "-pro"): + return "gpt-5.2-pro" + case strings.Contains(model, "-mini"): + return "gpt-5-mini" + case strings.Contains(model, "-nano"): + return "gpt-5-nano" + default: + return "gpt-5.2" + } +} + +func calculateCost(stats UsageStats, model string, serviceTier string) float64 { + pricing := getPricing(model, serviceTier) regularInputTokens := stats.InputTokens - stats.CachedTokens if regularInputTokens < 0 { @@ -252,12 +720,13 @@ func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON { } for i, combo := range u.Combinations { - totalCost := calculateCost(combo.Total, combo.Model) + totalCost := calculateCost(combo.Total, combo.Model, combo.ServiceTier) result.Costs.TotalUSD += totalCost comboJSON := CostCombinationJSON{ - Model: combo.Model, + Model: combo.Model, + ServiceTier: combo.ServiceTier, Total: UsageStatsJSON{ RequestCount: combo.Total.RequestCount, InputTokens: combo.Total.InputTokens, @@ -269,7 +738,7 @@ func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON { } for user, userStats := range combo.ByUser { - userCost := calculateCost(userStats, combo.Model) + userCost := calculateCost(userStats, combo.Model, combo.ServiceTier) result.Costs.ByUser[user] += userCost comboJSON.ByUser[user] = UsageStatsJSON{ @@ -318,6 +787,7 @@ func (u *AggregatedUsage) Load() error { u.Combinations = temp.Combinations for i := range u.Combinations { + u.Combinations[i].ServiceTier = normalizeServiceTier(u.Combinations[i].ServiceTier) if u.Combinations[i].ByUser == nil { u.Combinations[i].ByUser = make(map[string]UsageStats) } @@ -349,11 +819,13 @@ func (u *AggregatedUsage) Save() error { return err } -func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cachedTokens int64, user string) error { +func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cachedTokens int64, serviceTier string, user string) error { if model == "" { return E.New("model cannot be empty") } + normalizedServiceTier := normalizeServiceTier(serviceTier) + u.mutex.Lock() defer u.mutex.Unlock() @@ -361,7 +833,11 @@ func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cach var combo *CostCombination for i := range u.Combinations { - if u.Combinations[i].Model == model { + comboServiceTier := normalizeServiceTier(u.Combinations[i].ServiceTier) + if u.Combinations[i].ServiceTier != comboServiceTier { + u.Combinations[i].ServiceTier = comboServiceTier + } + if u.Combinations[i].Model == model && comboServiceTier == normalizedServiceTier { combo = &u.Combinations[i] break } @@ -369,9 +845,10 @@ func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cach if combo == nil { newCombo := CostCombination{ - Model: model, - Total: UsageStats{}, - ByUser: make(map[string]UsageStats), + Model: model, + ServiceTier: normalizedServiceTier, + Total: UsageStats{}, + ByUser: make(map[string]UsageStats), } u.Combinations = append(u.Combinations, newCombo) combo = &u.Combinations[len(u.Combinations)-1] diff --git a/small/hysteria/Makefile b/small/hysteria/Makefile index 2af2947678..3ca9567802 100644 --- a/small/hysteria/Makefile +++ b/small/hysteria/Makefile @@ -5,12 +5,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=hysteria -PKG_VERSION:=2.7.0 +PKG_VERSION:=2.7.1 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/apernet/hysteria/tar.gz/app/v$(PKG_VERSION)? -PKG_HASH:=3cac6adeccdac7cc8b353948e3cead1eb8606aa8ce74dac4853d821a22ceeba7 +PKG_HASH:=2b8e42e965eb1b5215efe58f06a1416b2379b025c79adc89c620c56488b4f08d PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-app-v$(PKG_VERSION) PKG_LICENSE:=MIT diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile index 02421d6cc8..c46cc9d455 100644 --- a/small/v2ray-geodata/Makefile +++ b/small/v2ray-geodata/Makefile @@ -21,13 +21,13 @@ define Download/geoip HASH:=838ab094bc01b9bafc849ce70c6f439dcb158d0c0dd41441ddb3c38d4d9ef563 endef -GEOSITE_VER:=20260220102217 +GEOSITE_VER:=20260224084600 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:=f1f70d67c7b971313140859c62d087b6c34c03041a7ba6ad3d9f06a217bcfdcf + HASH:=8a681eee7973fce2bfd68b698d868efec60cd77e1b5a34562514dc0a148cc9d5 endef GEOSITE_IRAN_VER:=202602230057 diff --git a/v2rayn/v2rayN/ServiceLib/Global.cs b/v2rayn/v2rayN/ServiceLib/Global.cs index e502435fdc..90721217dc 100644 --- a/v2rayn/v2rayN/ServiceLib/Global.cs +++ b/v2rayn/v2rayN/ServiceLib/Global.cs @@ -15,7 +15,6 @@ public class Global public const string CoreConfigFileName = "config.json"; public const string CorePreConfigFileName = "configPre.json"; public const string CoreSpeedtestConfigFileName = "configTest{0}.json"; - public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json"; public const string ClashMixinConfigFileName = "Mixin.yaml"; public const string NamespaceSample = "ServiceLib.Sample."; @@ -88,7 +87,6 @@ public class Global public const string SingboxLocalDNSTag = "local_local"; public const string SingboxHostsDNSTag = "hosts_dns"; public const string SingboxFakeDNSTag = "fake_dns"; - public const string SingboxEchDNSTag = "ech_dns"; public const int Hysteria2DefaultHopInt = 10; diff --git a/v2rayn/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayn/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 37176608be..e3e424845f 100644 --- a/v2rayn/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayn/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -254,6 +254,7 @@ public static class ConfigHandler item.CertSha = profileItem.CertSha; item.EchConfigList = profileItem.EchConfigList; item.EchForceQuery = profileItem.EchForceQuery; + item.Finalmask = profileItem.Finalmask; item.ProtoExtra = profileItem.ProtoExtra; } @@ -1122,6 +1123,7 @@ public static class ConfigHandler && AreEqual(o.Fingerprint, n.Fingerprint) && AreEqual(o.PublicKey, n.PublicKey) && AreEqual(o.ShortId, n.ShortId) + && AreEqual(o.Finalmask, n.Finalmask) && (!remarks || o.Remarks == n.Remarks); static bool AreEqual(string? a, string? b) @@ -1231,26 +1233,16 @@ public static class ConfigHandler /// Server node that might need pre-SOCKS /// Core type being used /// A SOCKS profile item or null if not needed - public static async Task GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType) + public static ProfileItem? GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType) { ProfileItem? itemSocks = null; if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun) { - var tun2SocksAddress = node.Address; - if (node.ConfigType.IsGroupType()) - { - var lstAddresses = (await GroupProfileManager.GetAllChildDomainAddresses(node)).ToList(); - if (lstAddresses.Count > 0) - { - tun2SocksAddress = Utils.List2String(lstAddresses); - } - } itemSocks = new ProfileItem() { CoreType = ECoreType.sing_box, ConfigType = EConfigType.SOCKS, Address = Global.Loopback, - SpiderX = tun2SocksAddress, // Tun2SocksAddress Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks) }; } @@ -1265,7 +1257,6 @@ public static class ConfigHandler Port = node.PreSocksPort.Value, }; } - await Task.CompletedTask; return itemSocks; } diff --git a/v2rayn/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayn/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index f51e2051d8..cebf2d3f7a 100644 --- a/v2rayn/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayn/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -7,27 +7,27 @@ public static class CoreConfigHandler { private static readonly string _tag = "CoreConfigHandler"; - public static async Task GenerateClientConfig(ProfileItem node, string? fileName) + public static async Task GenerateClientConfig(CoreConfigContext context, string? fileName) { var config = AppManager.Instance.Config; var result = new RetResult(); + var node = context.Node; if (node.ConfigType == EConfigType.Custom) { result = node.CoreType switch { ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName), - ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName), _ => await GenerateClientCustomConfig(node, fileName) }; } else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) { - result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node); + result = new CoreConfigSingboxService(context).GenerateClientConfigContent(); } else { - result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node); + result = new CoreConfigV2rayService(context).GenerateClientConfigContent(); } if (result.Success != true) { @@ -93,13 +93,21 @@ public static class CoreConfigHandler public static async Task GenerateClientSpeedtestConfig(Config config, string fileName, List selecteds, ECoreType coreType) { var result = new RetResult(); + var context = await BuildCoreConfigContext(config, new()); + var ids = selecteds.Where(serverTestItem => !serverTestItem.IndexId.IsNullOrEmpty()) + .Select(serverTestItem => serverTestItem.IndexId); + var nodes = await AppManager.Instance.GetProfileItemsByIndexIds(ids); + foreach (var node in nodes) + { + await FillNodeContext(context, node, false); + } if (coreType == ECoreType.sing_box) { - result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds); + result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(selecteds); } else if (coreType == ECoreType.Xray) { - result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds); + result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(selecteds); } if (result.Success != true) { @@ -109,20 +117,21 @@ public static class CoreConfigHandler return result; } - public static async Task GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName) + public static async Task GenerateClientSpeedtestConfig(Config config, CoreConfigContext context, ServerTestItem testItem, string fileName) { var result = new RetResult(); + var node = context.Node; var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest); var port = Utils.GetFreePort(initPort + testItem.QueueNum); testItem.Port = port; if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) { - result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port); + result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(port); } else { - result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port); + result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(port); } if (result.Success != true) { @@ -132,4 +141,127 @@ public static class CoreConfigHandler await File.WriteAllTextAsync(fileName, result.Data.ToString()); return result; } + + public static async Task BuildCoreConfigContext(Config config, ProfileItem node) + { + var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box ? ECoreType.sing_box : ECoreType.Xray; + var context = new CoreConfigContext() + { + Node = node, + AllProxiesMap = [], + AppConfig = config, + FullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(coreType), + IsTunEnabled = config.TunModeItem.EnableTun, + SimpleDnsItem = config.SimpleDNSItem, + ProtectDomainList = [], + ProtectSocksPort = 0, + RawDnsItem = await AppManager.Instance.GetDNSItem(coreType), + RoutingItem = await ConfigHandler.GetDefaultRouting(config), + }; + context = context with + { + Node = await FillNodeContext(context, node) + }; + if (!(context.RoutingItem?.RuleSet.IsNullOrEmpty() ?? true)) + { + var rules = JsonUtils.Deserialize>(context.RoutingItem?.RuleSet); + foreach (var ruleItem in rules.Where(ruleItem => !Global.OutboundTags.Contains(ruleItem.OutboundTag))) + { + var ruleOutboundNode = await AppManager.Instance.GetProfileItemViaRemarks(ruleItem.OutboundTag); + if (ruleOutboundNode != null) + { + var ruleOutboundNodeAct = await FillNodeContext(context, ruleOutboundNode, false); + context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = ruleOutboundNodeAct; + } + } + } + return context; + } + + private static async Task FillNodeContext(CoreConfigContext context, ProfileItem node, bool includeSubChain = true) + { + if (node.IndexId.IsNullOrEmpty()) + { + return node; + } + var newItems = new List { node }; + if (node.ConfigType.IsGroupType()) + { + var (groupChildList, _) = await GroupProfileManager.GetChildProfileItems(node); + foreach (var childItem in groupChildList.Where(childItem => !context.AllProxiesMap.ContainsKey(childItem.IndexId))) + { + await FillNodeContext(context, childItem, false); + } + node.SetProtocolExtra(node.GetProtocolExtra() with + { + ChildItems = Utils.List2String(groupChildList.Select(n => n.IndexId).ToList()), + }); + newItems.AddRange(groupChildList); + } + context.AllProxiesMap[node.IndexId] = node; + + foreach (var item in newItems) + { + var address = item.Address; + if (Utils.IsDomain(address)) + { + context.ProtectDomainList.Add(address); + } + + if (item.EchConfigList.IsNullOrEmpty()) + { + continue; + } + + var echQuerySni = item.Sni; + if (item.StreamSecurity == Global.StreamSecurity + && item.EchConfigList?.Contains("://") == true) + { + var idx = item.EchConfigList.IndexOf('+'); + echQuerySni = idx > 0 ? item.EchConfigList[..idx] : item.Sni; + } + if (!Utils.IsDomain(echQuerySni)) + { + continue; + } + context.ProtectDomainList.Add(echQuerySni); + } + + if (!includeSubChain || node.Subid.IsNullOrEmpty()) + { + return node; + } + + var subItem = await AppManager.Instance.GetSubItem(node.Subid); + if (subItem == null) + { + return node; + } + var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); + var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); + if (prevNode is null && nextNode is null) + { + return node; + } + + var prevNodeAct = prevNode is null ? null : await FillNodeContext(context, prevNode, false); + var nextNodeAct = nextNode is null ? null : await FillNodeContext(context, nextNode, false); + + // Build new proxy chain node + var chainNode = new ProfileItem() + { + IndexId = $"inner-{Utils.GetGuid(false)}", + ConfigType = EConfigType.ProxyChain, + CoreType = node.CoreType ?? ECoreType.Xray, + }; + List childItems = [prevNodeAct?.IndexId, node.IndexId, nextNodeAct?.IndexId]; + var chainExtraItem = chainNode.GetProtocolExtra() with + { + GroupType = chainNode.ConfigType.ToString(), + ChildItems = string.Join(",", childItems.Where(x => !x.IsNullOrEmpty())), + }; + chainNode.SetProtocolExtra(chainExtraItem); + context.AllProxiesMap[chainNode.IndexId] = chainNode; + return chainNode; + } } diff --git a/v2rayn/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs b/v2rayn/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs index bfafce135e..b620cfe909 100644 --- a/v2rayn/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs +++ b/v2rayn/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs @@ -73,6 +73,19 @@ public class BaseFmt { dicQuery.Add("pcs", Utils.UrlEncode(item.CertSha)); } + if (item.Finalmask.IsNotEmpty()) + { + var node = JsonUtils.ParseJson(item.Finalmask); + var finalmask = node != null + ? JsonUtils.Serialize(node, new JsonSerializerOptions + { + WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }) + : item.Finalmask; + dicQuery.Add("fm", Utils.UrlEncode(finalmask)); + } dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp)); @@ -214,6 +227,24 @@ public class BaseFmt item.EchConfigList = GetQueryDecoded(query, "ech"); item.CertSha = GetQueryDecoded(query, "pcs"); + var finalmaskDecoded = GetQueryDecoded(query, "fm"); + if (finalmaskDecoded.IsNotEmpty()) + { + var node = JsonUtils.ParseJson(finalmaskDecoded); + item.Finalmask = node != null + ? JsonUtils.Serialize(node, new JsonSerializerOptions + { + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }) + : finalmaskDecoded; + } + else + { + item.Finalmask = string.Empty; + } + if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1")) { item.AllowInsecure = Global.AllowInsecure.First(); diff --git a/v2rayn/v2rayN/ServiceLib/Manager/AppManager.cs b/v2rayn/v2rayN/ServiceLib/Manager/AppManager.cs index 0e47156842..e773c2ef9c 100644 --- a/v2rayn/v2rayN/ServiceLib/Manager/AppManager.cs +++ b/v2rayn/v2rayN/ServiceLib/Manager/AppManager.cs @@ -230,6 +230,18 @@ public sealed class AppManager return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.IndexId == indexId); } + public async Task> GetProfileItemsByIndexIds(IEnumerable indexIds) + { + var ids = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList(); + if (ids.Count == 0) + { + return []; + } + return await SQLiteHelper.Instance.TableAsync() + .Where(it => ids.Contains(it.IndexId)) + .ToListAsync(); + } + public async Task GetProfileItemViaRemarks(string? remarks) { if (remarks.IsNullOrEmpty()) diff --git a/v2rayn/v2rayN/ServiceLib/Manager/CertPemManager.cs b/v2rayn/v2rayN/ServiceLib/Manager/CertPemManager.cs index bb8df227ae..b1c8d82a44 100644 --- a/v2rayn/v2rayN/ServiceLib/Manager/CertPemManager.cs +++ b/v2rayn/v2rayN/ServiceLib/Manager/CertPemManager.cs @@ -215,7 +215,7 @@ public class CertPemManager using var client = new TcpClient(); await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token); - using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); + await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); var sslOptions = new SslClientAuthenticationOptions { @@ -262,7 +262,7 @@ public class CertPemManager using var client = new TcpClient(); await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token); - using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); + await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); var sslOptions = new SslClientAuthenticationOptions { @@ -280,11 +280,7 @@ public class CertPemManager var chain = new X509Chain(); chain.Build(certChain); - foreach (var element in chain.ChainElements) - { - var pem = ExportCertToPem(element.Certificate); - pemList.Add(pem); - } + pemList.AddRange(chain.ChainElements.Select(element => ExportCertToPem(element.Certificate))); return (pemList, null); } diff --git a/v2rayn/v2rayN/ServiceLib/Manager/CoreManager.cs b/v2rayn/v2rayN/ServiceLib/Manager/CoreManager.cs index 5324096069..86934c456d 100644 --- a/v2rayn/v2rayN/ServiceLib/Manager/CoreManager.cs +++ b/v2rayn/v2rayN/ServiceLib/Manager/CoreManager.cs @@ -66,7 +66,9 @@ public class CoreManager } var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); - var result = await CoreConfigHandler.GenerateClientConfig(node, fileName); + var context = await CoreConfigHandler.BuildCoreConfigContext(_config, node); + context = context with { IsTunEnabled = _config.TunModeItem.EnableTun }; + var result = await CoreConfigHandler.GenerateClientConfig(context, fileName); if (result.Success != true) { await UpdateFunc(true, result.Msg); @@ -85,8 +87,8 @@ public class CoreManager await WindowsUtils.RemoveTunDevice(); } - await CoreStart(node); - await CoreStartPreService(node); + await CoreStart(context); + await CoreStartPreService(context); if (_processService != null) { await UpdateFunc(true, $"{node.GetSummary()}"); @@ -122,7 +124,8 @@ public class CoreManager var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); var configPath = Utils.GetBinConfigPath(fileName); - var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath); + var context = await CoreConfigHandler.BuildCoreConfigContext(_config, node); + var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, context, testItem, configPath); if (result.Success != true) { return null; @@ -165,8 +168,9 @@ public class CoreManager #region Private - private async Task CoreStart(ProfileItem node) + private async Task CoreStart(CoreConfigContext context) { + var node = context.Node; var coreType = AppManager.Instance.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType); @@ -179,17 +183,20 @@ public class CoreManager _processService = proc; } - private async Task CoreStartPreService(ProfileItem node) + private async Task CoreStartPreService(CoreConfigContext context) { + var node = context.Node; if (_processService != null && !_processService.HasExited) { var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType); - var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType); + var itemSocks = ConfigHandler.GetPreSocksItem(_config, node, coreType); if (itemSocks != null) { var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box; var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); - var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName); + var itemSocksContext = await CoreConfigHandler.BuildCoreConfigContext(_config, itemSocks); + itemSocksContext.ProtectDomainList.AddRangeSafe(context.ProtectDomainList); + var result = await CoreConfigHandler.GenerateClientConfig(itemSocksContext, fileName); if (result.Success) { var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType); diff --git a/v2rayn/v2rayN/ServiceLib/Manager/GroupProfileManager.cs b/v2rayn/v2rayN/ServiceLib/Manager/GroupProfileManager.cs index 53ef042918..5e87f0e653 100644 --- a/v2rayn/v2rayN/ServiceLib/Manager/GroupProfileManager.cs +++ b/v2rayn/v2rayN/ServiceLib/Manager/GroupProfileManager.cs @@ -79,7 +79,7 @@ public class GroupProfileManager { if (protocolExtra == null) { - return new(); + return []; } var items = new List(); @@ -93,27 +93,44 @@ public class GroupProfileManager { if (extra == null || extra.ChildItems.IsNullOrEmpty()) { - return new(); + return []; } - var childProfiles = (await Task.WhenAll( - (Utils.String2List(extra.ChildItems) ?? new()) - .Where(p => !p.IsNullOrEmpty()) - .Select(AppManager.Instance.GetProfileItem) - )) - .Where(p => - p != null && - p.IsValid() && - p.ConfigType != EConfigType.Custom - ) - .ToList(); - return childProfiles ?? new(); + var childProfileIds = Utils.String2List(extra.ChildItems) + ?.Where(p => !string.IsNullOrEmpty(p)) + .ToList() ?? []; + if (childProfileIds.Count == 0) + { + return []; + } + + var childProfiles = await AppManager.Instance.GetProfileItemsByIndexIds(childProfileIds); + if (childProfiles == null || childProfiles.Count == 0) + { + return []; + } + + var profileMap = childProfiles + .Where(p => p != null && !p.IndexId.IsNullOrEmpty()) + .GroupBy(p => p!.IndexId!) + .ToDictionary(g => g.Key, g => g.First()); + + var ordered = new List(childProfileIds.Count); + foreach (var id in childProfileIds) + { + if (id != null && profileMap.TryGetValue(id, out var item) && item != null) + { + ordered.Add(item); + } + } + + return ordered; } private static async Task> GetSubChildProfileItems(ProtocolExtraItem? extra) { if (extra == null || extra.SubChildItems.IsNullOrEmpty()) { - return new(); + return []; } var childProfiles = await AppManager.Instance.ProfileItems(extra.SubChildItems ?? string.Empty); @@ -123,59 +140,30 @@ public class GroupProfileManager !p.ConfigType.IsComplexType() && (extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter)) ) - .ToList() ?? new(); + .ToList() ?? []; } - public static async Task> GetAllChildDomainAddresses(ProfileItem profileItem) + public static async Task> GetAllChildProfileItems(ProfileItem profileItem) { - var childAddresses = new HashSet(); - var (childItems, _) = await GetChildProfileItems(profileItem); - foreach (var child in childItems) - { - if (!child.IsComplex()) - { - childAddresses.Add(child.Address); - } - else if (child.ConfigType.IsGroupType()) - { - var subAddresses = await GetAllChildDomainAddresses(child); - foreach (var addr in subAddresses) - { - childAddresses.Add(addr); - } - } - } - return childAddresses; + var allChildItems = new List(); + var visited = new HashSet(); + + await CollectChildItems(profileItem, allChildItems, visited); + + return allChildItems; } - public static async Task> GetAllChildEchQuerySni(ProfileItem profileItem) + private static async Task CollectChildItems(ProfileItem profileItem, List allChildItems, HashSet visited) { - var childAddresses = new HashSet(); var (childItems, _) = await GetChildProfileItems(profileItem); - foreach (var childNode in childItems) + foreach (var child in childItems.Where(child => visited.Add(child.IndexId))) { - if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty()) + allChildItems.Add(child); + + if (child.ConfigType.IsGroupType()) { - if (childNode.StreamSecurity == Global.StreamSecurity - && childNode.EchConfigList?.Contains("://") == true) - { - var idx = childNode.EchConfigList.IndexOf('+'); - childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni); - } - else - { - childAddresses.Add(childNode.Sni); - } - } - else if (childNode.ConfigType.IsGroupType()) - { - var subAddresses = await GetAllChildDomainAddresses(childNode); - foreach (var addr in subAddresses) - { - childAddresses.Add(addr); - } + await CollectChildItems(child, allChildItems, visited); } } - return childAddresses; } } diff --git a/v2rayn/v2rayN/ServiceLib/Models/CoreConfigContext.cs b/v2rayn/v2rayN/ServiceLib/Models/CoreConfigContext.cs new file mode 100644 index 0000000000..9551d39dcc --- /dev/null +++ b/v2rayn/v2rayN/ServiceLib/Models/CoreConfigContext.cs @@ -0,0 +1,17 @@ +namespace ServiceLib.Models; + +public record CoreConfigContext +{ + public required ProfileItem Node { get; init; } + public RoutingItem? RoutingItem { get; init; } + public DNSItem? RawDnsItem { get; init; } + public SimpleDNSItem SimpleDnsItem { get; init; } = new(); + public Dictionary AllProxiesMap { get; init; } = new(); + public Config AppConfig { get; init; } = new(); + public FullConfigTemplateItem? FullConfigTemplate { get; init; } = new(); + + // TUN Compatibility + public bool IsTunEnabled { get; init; } = false; + public HashSet ProtectDomainList { get; init; } = new(); + public int ProtectSocksPort { get; init; } = 0; +} diff --git a/v2rayn/v2rayN/ServiceLib/Models/ProfileItem.cs b/v2rayn/v2rayN/ServiceLib/Models/ProfileItem.cs index 6e9d2a30c0..50ac8b9094 100644 --- a/v2rayn/v2rayN/ServiceLib/Models/ProfileItem.cs +++ b/v2rayn/v2rayN/ServiceLib/Models/ProfileItem.cs @@ -178,6 +178,7 @@ public class ProfileItem public string CertSha { get; set; } public string EchConfigList { get; set; } public string EchForceQuery { get; set; } + public string Finalmask { get; set; } public string ProtoExtra { get; set; } diff --git a/v2rayn/v2rayN/ServiceLib/Models/SingboxConfig.cs b/v2rayn/v2rayN/ServiceLib/Models/SingboxConfig.cs index c0cd2e08d5..c1eaa3c829 100644 --- a/v2rayn/v2rayN/ServiceLib/Models/SingboxConfig.cs +++ b/v2rayn/v2rayN/ServiceLib/Models/SingboxConfig.cs @@ -255,9 +255,8 @@ public class Server4Sbox : BaseServer4Sbox // public List? path { get; set; } // hosts public Dictionary>? predefined { get; set; } - // Deprecated + // Deprecated in sing-box 1.12.0 , kept for backward compatibility public string? address { get; set; } - public string? address_resolver { get; set; } public string? address_strategy { get; set; } public string? strategy { get; set; } diff --git a/v2rayn/v2rayN/ServiceLib/Models/V2rayConfig.cs b/v2rayn/v2rayN/ServiceLib/Models/V2rayConfig.cs index 34456a8e14..53d345632d 100644 --- a/v2rayn/v2rayN/ServiceLib/Models/V2rayConfig.cs +++ b/v2rayn/v2rayN/ServiceLib/Models/V2rayConfig.cs @@ -341,7 +341,7 @@ public class StreamSettings4Ray public HysteriaSettings4Ray? hysteriaSettings { get; set; } - public FinalMask4Ray? finalmask { get; set; } + public Finalmask4Ray? finalmask { get; set; } public Sockopt4Ray? sockopt { get; set; } } @@ -476,7 +476,7 @@ public class HysteriaUdpHop4Ray public string? interval { get; set; } } -public class FinalMask4Ray +public class Finalmask4Ray { public List? tcp { get; set; } public List? udp { get; set; } @@ -485,7 +485,7 @@ public class FinalMask4Ray public class Mask4Ray { public string type { get; set; } - public MaskSettings4Ray? settings { get; set; } + public object? settings { get; set; } } public class MaskSettings4Ray diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 6f223693c9..20637ed559 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -2916,6 +2916,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Finalmask 的本地化字符串。 + /// + public static string TbFinalmask { + get { + return ResourceManager.GetString("TbFinalmask", resourceCulture); + } + } + /// /// 查找类似 Fingerprint 的本地化字符串。 /// diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index dbce6ac432..c93a9292ea 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + Finalmask + \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fr.resx index 005a51d55e..b2e636a9dd 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1668,4 +1668,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + Finalmask + \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 3d16959053..9749f552b0 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + Finalmask + \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.resx index 041a103bb0..1ff10bec5c 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + Finalmask + \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 66ebef79af..f65a967d5a 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + Finalmask + \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index c89366683e..e185636ccc 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1668,4 +1668,7 @@ 子配置项预览 + + Finalmask + \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 2ee4dc4fb7..8b2c4cff19 100644 --- a/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayn/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1668,4 +1668,7 @@ Configuration item preview + + Finalmask + \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Sample/SampleClientConfig b/v2rayn/v2rayN/ServiceLib/Sample/SampleClientConfig index 407a130738..b4ebf4919b 100644 --- a/v2rayn/v2rayN/ServiceLib/Sample/SampleClientConfig +++ b/v2rayn/v2rayN/ServiceLib/Sample/SampleClientConfig @@ -1,4 +1,4 @@ -{ +{ "log": { "access": "Vaccess.log", "error": "Verror.log", @@ -6,34 +6,6 @@ }, "inbounds": [], "outbounds": [ - { - "tag": "proxy", - "protocol": "vmess", - "settings": { - "vnext": [{ - "address": "", - "port": 0, - "users": [{ - "id": "", - "security": "auto" - }] - }], - "servers": [{ - "address": "", - "method": "", - "ota": false, - "password": "", - "port": 0, - "level": 1 - }] - }, - "streamSettings": { - "network": "tcp" - }, - "mux": { - "enabled": false - } - }, { "protocol": "freedom", "tag": "direct" diff --git a/v2rayn/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig b/v2rayn/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig index b07fd72c13..7ff36ec936 100644 --- a/v2rayn/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig +++ b/v2rayn/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig @@ -5,19 +5,12 @@ }, "inbounds": [], "outbounds": [ - { - "type": "vless", - "tag": "proxy", - "server": "", - "server_port": 443 - }, { "type": "direct", "tag": "direct" } ], "route": { - "rules": [ - ] + "rules": [] } } \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs index 397e934060..c0ce715452 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs @@ -1,43 +1,34 @@ namespace ServiceLib.Services.CoreConfig; -public partial class CoreConfigSingboxService(Config config) +public partial class CoreConfigSingboxService(CoreConfigContext context) { - private readonly Config _config = config; private static readonly string _tag = "CoreConfigSingboxService"; + private readonly Config _config = context.AppConfig; + private readonly ProfileItem _node = context.Node; + + private SingboxConfig _coreConfig = new(); #region public gen function - public async Task GenerateClientConfigContent(ProfileItem node) + public RetResult GenerateClientConfigContent() { var ret = new RetResult(); try { - if (node == null - || !node.IsValid()) + if (_node == null + || !_node.IsValid()) { ret.Msg = ResUI.CheckServerSettings; return ret; } - if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) + if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) { - ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; return ret; } ret.Msg = ResUI.InitialConfiguration; - if (node.ConfigType.IsGroupType()) - { - switch (node.ConfigType) - { - case EConfigType.PolicyGroup: - return await GenerateClientMultipleLoadConfig(node); - - case EConfigType.ProxyChain: - return await GenerateClientChainConfig(node); - } - } - var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); if (result.IsNullOrEmpty()) { @@ -45,44 +36,31 @@ public partial class CoreConfigSingboxService(Config config) return ret; } - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } - await GenLog(singboxConfig); + GenLog(); - await GenInbounds(singboxConfig); + GenInbounds(); - if (node.ConfigType == EConfigType.WireGuard) - { - singboxConfig.outbounds.RemoveAt(0); - var endpoints = new Endpoints4Sbox(); - await GenEndpoint(node, endpoints); - endpoints.tag = Global.ProxyTag; - singboxConfig.endpoints = new() { endpoints }; - } - else - { - await GenOutbound(node, singboxConfig.outbounds.First()); - } + GenOutbounds(); - await GenMoreOutbounds(node, singboxConfig); + GenRouting(); - await GenRouting(singboxConfig); + GenDns(); - await GenDns(node, singboxConfig); + GenExperimental(); - await GenExperimental(singboxConfig); - - await ConvertGeo2Ruleset(singboxConfig); + ConvertGeo2Ruleset(); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; - ret.Data = await ApplyFullConfigTemplate(singboxConfig); + ret.Data = ApplyFullConfigTemplate(); return ret; } catch (Exception ex) @@ -93,17 +71,11 @@ public partial class CoreConfigSingboxService(Config config) } } - public async Task GenerateClientSpeedtestConfig(List selecteds) + public RetResult GenerateClientSpeedtestConfig(List selecteds) { var ret = new RetResult(); try { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - ret.Msg = ResUI.InitialConfiguration; var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); @@ -114,8 +86,8 @@ public partial class CoreConfigSingboxService(Config config) return ret; } - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; @@ -133,10 +105,10 @@ public partial class CoreConfigSingboxService(Config config) Logging.SaveLog(_tag, ex); } - await GenLog(singboxConfig); - //GenDns(new(), singboxConfig); - singboxConfig.inbounds.Clear(); - singboxConfig.outbounds.RemoveAt(0); + GenLog(); + GenMinimizedDns(); + _coreConfig.inbounds.Clear(); + _coreConfig.outbounds.RemoveAt(0); var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest); @@ -150,7 +122,7 @@ public partial class CoreConfigSingboxService(Config config) { continue; } - var item = await AppManager.Instance.GetProfileItem(it.IndexId); + var item = context.AllProxiesMap.GetValueOrDefault(it.IndexId); if (item is null || item.IsComplex() || !item.IsValid()) { continue; @@ -190,26 +162,11 @@ public partial class CoreConfigSingboxService(Config config) type = EInboundProtocol.mixed.ToString(), }; inbound.tag = inbound.type + inbound.listen_port.ToString(); - singboxConfig.inbounds.Add(inbound); + _coreConfig.inbounds.Add(inbound); - //outbound - var server = await GenServer(item); - if (server is null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } var tag = Global.ProxyTag + inbound.listen_port.ToString(); - server.tag = tag; - if (server is Endpoints4Sbox endpoint) - { - singboxConfig.endpoints ??= new(); - singboxConfig.endpoints.Add(endpoint); - } - else if (server is Outbound4Sbox outbound) - { - singboxConfig.outbounds.Add(outbound); - } + var serverList = new CoreConfigSingboxService(context with { Node = item }).BuildAllProxyOutbounds(tag); + FillRangeProxy(serverList, _coreConfig, false); //rule Rule4Sbox rule = new() @@ -217,25 +174,11 @@ public partial class CoreConfigSingboxService(Config config) inbound = new List { inbound.tag }, outbound = tag }; - singboxConfig.route.rules.Add(rule); + _coreConfig.route.rules.Add(rule); } - var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); - if (rawDNSItem != null && rawDNSItem.Enabled == true) - { - await GenDnsDomainsCompatible(singboxConfig, rawDNSItem); - } - else - { - await GenDnsDomains(singboxConfig, _config.SimpleDNSItem); - } - singboxConfig.route.default_domain_resolver = new() - { - server = Global.SingboxLocalDNSTag, - }; - ret.Success = true; - ret.Data = JsonUtils.Serialize(singboxConfig); + ret.Data = JsonUtils.Serialize(_coreConfig); return ret; } catch (Exception ex) @@ -246,20 +189,20 @@ public partial class CoreConfigSingboxService(Config config) } } - public async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) + public RetResult GenerateClientSpeedtestConfig(int port) { var ret = new RetResult(); try { - if (node == null - || !node.IsValid()) + if (_node == null + || !_node.IsValid()) { ret.Msg = ResUI.CheckServerSettings; return ret; } - if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) + if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) { - ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; return ret; } @@ -272,44 +215,20 @@ public partial class CoreConfigSingboxService(Config config) return ret; } - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } - await GenLog(singboxConfig); - if (node.ConfigType == EConfigType.WireGuard) - { - singboxConfig.outbounds.RemoveAt(0); - var endpoints = new Endpoints4Sbox(); - await GenEndpoint(node, endpoints); - endpoints.tag = Global.ProxyTag; - singboxConfig.endpoints = new() { endpoints }; - } - else - { - await GenOutbound(node, singboxConfig.outbounds.First()); - } - await GenMoreOutbounds(node, singboxConfig); - var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); - if (item != null && item.Enabled == true) - { - await GenDnsDomainsCompatible(singboxConfig, item); - } - else - { - await GenDnsDomains(singboxConfig, _config.SimpleDNSItem); - } - singboxConfig.route.default_domain_resolver = new() - { - server = Global.SingboxLocalDNSTag, - }; + GenLog(); + GenOutbounds(); + GenMinimizedDns(); - singboxConfig.route.rules.Clear(); - singboxConfig.inbounds.Clear(); - singboxConfig.inbounds.Add(new() + _coreConfig.route.rules.Clear(); + _coreConfig.inbounds.Clear(); + _coreConfig.inbounds.Add(new() { tag = $"{EInboundProtocol.mixed}{port}", listen = Global.Loopback, @@ -319,202 +238,7 @@ public partial class CoreConfigSingboxService(Config config) ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; - ret.Data = JsonUtils.Serialize(singboxConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientMultipleLoadConfig(ProfileItem parentNode) - { - var ret = new RetResult(); - try - { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - singboxConfig.outbounds.RemoveAt(0); - - await GenLog(singboxConfig); - await GenInbounds(singboxConfig); - - var groupRet = await GenGroupOutbound(parentNode, singboxConfig); - if (groupRet != 0) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenRouting(singboxConfig); - await GenExperimental(singboxConfig); - await GenDns(parentNode, singboxConfig); - await ConvertGeo2Ruleset(singboxConfig); - - ret.Success = true; - - ret.Data = await ApplyFullConfigTemplate(singboxConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientChainConfig(ProfileItem parentNode) - { - var ret = new RetResult(); - try - { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - singboxConfig.outbounds.RemoveAt(0); - - await GenLog(singboxConfig); - await GenInbounds(singboxConfig); - - var groupRet = await GenGroupOutbound(parentNode, singboxConfig); - if (groupRet != 0) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenRouting(singboxConfig); - await GenExperimental(singboxConfig); - await GenDns(parentNode, singboxConfig); - await ConvertGeo2Ruleset(singboxConfig); - - ret.Success = true; - - ret.Data = await ApplyFullConfigTemplate(singboxConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) - { - var ret = new RetResult(); - if (node == null || fileName is null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - try - { - if (node == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - if (File.Exists(fileName)) - { - File.Delete(fileName); - } - - var addressFileName = node.Address; - if (addressFileName.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - if (!File.Exists(addressFileName)) - { - addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName); - } - if (!File.Exists(addressFileName)) - { - ret.Msg = ResUI.FailedReadConfiguration + "1"; - return ret; - } - - if (node.Address == Global.CoreMultipleLoadConfigFileName) - { - var txtFile = File.ReadAllText(addressFileName); - var singboxConfig = JsonUtils.Deserialize(txtFile); - if (singboxConfig == null) - { - File.Copy(addressFileName, fileName); - } - else - { - await GenInbounds(singboxConfig); - await GenExperimental(singboxConfig); - - var content = JsonUtils.Serialize(singboxConfig, true); - await File.WriteAllTextAsync(fileName, content); - } - } - else - { - File.Copy(addressFileName, fileName); - } - - //check again - if (!File.Exists(fileName)) - { - ret.Msg = ResUI.FailedReadConfiguration + "2"; - return ret; - } - - ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); - ret.Success = true; + ret.Data = JsonUtils.Serialize(_coreConfig); return ret; } catch (Exception ex) diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs index 6fe9b35a0f..25b2970191 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs @@ -2,29 +2,29 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task ApplyFullConfigTemplate(SingboxConfig singboxConfig) + private string ApplyFullConfigTemplate() { - var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box); - if (fullConfigTemplate == null || !fullConfigTemplate.Enabled) + var fullConfigTemplate = context.FullConfigTemplate; + if (fullConfigTemplate is not { Enabled: true }) { - return JsonUtils.Serialize(singboxConfig); + return JsonUtils.Serialize(_coreConfig); } - var fullConfigTemplateItem = _config.TunModeItem.EnableTun ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config; + var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config; if (fullConfigTemplateItem.IsNullOrEmpty()) { - return JsonUtils.Serialize(singboxConfig); + return JsonUtils.Serialize(_coreConfig); } var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem); if (fullConfigTemplateNode == null) { - return JsonUtils.Serialize(singboxConfig); + return JsonUtils.Serialize(_coreConfig); } // Process outbounds - var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray(); - foreach (var outbound in singboxConfig.outbounds) + var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : []; + foreach (var outbound in _coreConfig.outbounds) { if (outbound.type.ToLower() is "direct" or "block") { @@ -42,10 +42,10 @@ public partial class CoreConfigSingboxService fullConfigTemplateNode["outbounds"] = customOutboundsNode; // Process endpoints - if (singboxConfig.endpoints != null && singboxConfig.endpoints.Count > 0) + if (_coreConfig.endpoints != null && _coreConfig.endpoints.Count > 0) { - var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : new JsonArray(); - foreach (var endpoint in singboxConfig.endpoints) + var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : []; + foreach (var endpoint in _coreConfig.endpoints) { if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty()) { @@ -56,6 +56,6 @@ public partial class CoreConfigSingboxService fullConfigTemplateNode["endpoints"] = customEndpointsNode; } - return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode)); + return JsonUtils.Serialize(fullConfigTemplateNode); } } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs index 84df7538ee..b4ddaf7f89 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs @@ -2,25 +2,25 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task GenDns(ProfileItem? node, SingboxConfig singboxConfig) + private void GenDns() { try { - var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); - if (item != null && item.Enabled == true) + var item = context.RawDnsItem; + if (item is { Enabled: true }) { - return await GenDnsCompatible(node, singboxConfig); + GenDnsCustom(); + return; } - var simpleDNSItem = _config.SimpleDNSItem; - await GenDnsServers(node, singboxConfig, simpleDNSItem); - await GenDnsRules(node, singboxConfig, simpleDNSItem); + GenDnsServers(); + GenDnsRules(); - singboxConfig.dns ??= new Dns4Sbox(); - singboxConfig.dns.independent_cache = true; + _coreConfig.dns ??= new Dns4Sbox(); + _coreConfig.dns.independent_cache = true; // final dns - var routing = await ConfigHandler.GetDefaultRouting(_config); + var routing = context.RoutingItem; var useDirectDns = false; if (routing != null) { @@ -32,35 +32,34 @@ public partial class CoreConfigSingboxService lastRule.Network == "tcp,udp" || lastRule.Ip?.Contains("0.0.0.0/0") == true); } - singboxConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag; - if ((!useDirectDns) && simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false) + _coreConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag; + var simpleDnsItem = context.SimpleDnsItem; + if ((!useDirectDns) && simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false) { - singboxConfig.dns.rules.Add(new() + _coreConfig.dns.rules.Add(new() { server = Global.SingboxFakeDNSTag, query_type = new List { 1, 28 }, // A and AAAA rewrite_ttl = 1, }); } - - await GenOutboundDnsRule(node, singboxConfig); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenDnsServers(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) + private void GenDnsServers() { - var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem); + var simpleDnsItem = context.SimpleDnsItem; + var finalDns = GenBootstrapDns(); - var directDns = ParseDnsAddress(simpleDNSItem.DirectDNS); + var directDns = ParseDnsAddress(simpleDnsItem.DirectDNS ?? Global.DomainDirectDNSAddress.First()); directDns.tag = Global.SingboxDirectDNSTag; directDns.domain_resolver = Global.SingboxLocalDNSTag; - var remoteDns = ParseDnsAddress(simpleDNSItem.RemoteDNS); + var remoteDns = ParseDnsAddress(simpleDnsItem.RemoteDNS ?? Global.DomainRemoteDNSAddress.First()); remoteDns.tag = Global.SingboxRemoteDNSTag; remoteDns.detour = Global.ProxyTag; remoteDns.domain_resolver = Global.SingboxLocalDNSTag; @@ -71,12 +70,12 @@ public partial class CoreConfigSingboxService type = "hosts", predefined = new(), }; - if (simpleDNSItem.AddCommonHosts == true) + if (simpleDnsItem.AddCommonHosts == true) { hostsDns.predefined = Global.PredefinedHosts; } - if (simpleDNSItem.UseSystemHosts == true) + if (simpleDnsItem.UseSystemHosts == true) { var systemHosts = Utils.GetSystemHosts(); if (systemHosts != null && systemHosts.Count > 0) @@ -88,9 +87,9 @@ public partial class CoreConfigSingboxService } } - foreach (var kvp in Utils.ParseHostsToDictionary(simpleDNSItem.Hosts)) + foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts)) { - hostsDns.predefined[kvp.Key] = kvp.Value.Where(s => Utils.IsIpAddress(s)).ToList(); + hostsDns.predefined[kvp.Key] = kvp.Value.Where(Utils.IsIpAddress).ToList(); } foreach (var host in hostsDns.predefined) @@ -109,14 +108,14 @@ public partial class CoreConfigSingboxService } } - singboxConfig.dns ??= new Dns4Sbox(); - singboxConfig.dns.servers ??= []; - singboxConfig.dns.servers.Add(remoteDns); - singboxConfig.dns.servers.Add(directDns); - singboxConfig.dns.servers.Add(hostsDns); + _coreConfig.dns ??= new Dns4Sbox(); + _coreConfig.dns.servers ??= []; + _coreConfig.dns.servers.Add(remoteDns); + _coreConfig.dns.servers.Add(directDns); + _coreConfig.dns.servers.Add(hostsDns); // fake ip - if (simpleDNSItem.FakeIP == true) + if (simpleDnsItem.FakeIP == true) { var fakeip = new Server4Sbox { @@ -125,68 +124,50 @@ public partial class CoreConfigSingboxService inet4_range = "198.18.0.0/15", inet6_range = "fc00::/18", }; - singboxConfig.dns.servers.Add(fakeip); + _coreConfig.dns.servers.Add(fakeip); } - - // ech - var (_, dnsServer) = ParseEchParam(node?.EchConfigList); - if (dnsServer is not null) - { - dnsServer.tag = Global.SingboxEchDNSTag; - if (dnsServer.server is not null - && hostsDns.predefined.ContainsKey(dnsServer.server)) - { - dnsServer.domain_resolver = Global.SingboxHostsDNSTag; - } - else - { - dnsServer.domain_resolver = Global.SingboxLocalDNSTag; - } - singboxConfig.dns.servers.Add(dnsServer); - } - else if (node?.ConfigType.IsGroupType() == true) - { - var echDnsObject = JsonUtils.DeepCopy(directDns); - echDnsObject.tag = Global.SingboxEchDNSTag; - singboxConfig.dns.servers.Add(echDnsObject); - } - - return await Task.FromResult(0); } - private async Task GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem) + private Server4Sbox GenBootstrapDns() { - var finalDns = ParseDnsAddress(simpleDNSItem.BootstrapDNS); + var finalDns = ParseDnsAddress(context.SimpleDnsItem?.BootstrapDNS ?? Global.DomainPureIPDNSAddress.First()); finalDns.tag = Global.SingboxLocalDNSTag; - singboxConfig.dns ??= new Dns4Sbox(); - singboxConfig.dns.servers ??= new List(); - singboxConfig.dns.servers.Add(finalDns); - return await Task.FromResult(finalDns); + _coreConfig.dns ??= new Dns4Sbox(); + _coreConfig.dns.servers ??= []; + _coreConfig.dns.servers.Add(finalDns); + return finalDns; } - private async Task GenDnsRules(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) + private void GenDnsRules() { - singboxConfig.dns ??= new Dns4Sbox(); - singboxConfig.dns.rules ??= new List(); + var simpleDnsItem = context.SimpleDnsItem; + _coreConfig.dns ??= new Dns4Sbox(); + _coreConfig.dns.rules ??= []; - singboxConfig.dns.rules.AddRange(new[] + _coreConfig.dns.rules.AddRange(new[] { new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag }, new Rule4Sbox + { + server = Global.SingboxDirectDNSTag, + strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom), + domain = context.ProtectDomainList.ToList(), + }, + new Rule4Sbox { server = Global.SingboxRemoteDNSTag, - strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Proxy), + strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy), clash_mode = ERuleMode.Global.ToString() }, new Rule4Sbox { server = Global.SingboxDirectDNSTag, - strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Freedom), + strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom), clash_mode = ERuleMode.Direct.ToString() } }); - foreach (var kvp in Utils.ParseHostsToDictionary(simpleDNSItem.Hosts)) + foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts)) { var predefined = kvp.Value.First(); if (predefined.IsNullOrEmpty() || Utils.IsIpAddress(predefined)) @@ -197,7 +178,7 @@ public partial class CoreConfigSingboxService { // xray syntactic sugar for predefined // etc. #0 -> NOERROR - singboxConfig.dns.rules.Add(new() + _coreConfig.dns.rules.Add(new() { query_type = [1, 28], domain = [kvp.Key], @@ -225,38 +206,13 @@ public partial class CoreConfigSingboxService }; if (ParseV2Domain(kvp.Key, rule)) { - singboxConfig.dns.rules.Add(rule); + _coreConfig.dns.rules.Add(rule); } } - var (ech, _) = ParseEchParam(node?.EchConfigList); - if (ech is not null) + if (simpleDnsItem.BlockBindingQuery == true) { - var echDomain = ech.query_server_name ?? node?.Sni; - singboxConfig.dns.rules.Add(new() - { - query_type = [64, 65], - server = Global.SingboxEchDNSTag, - domain = echDomain is not null ? new List { echDomain } : null, - }); - } - else if (node?.ConfigType.IsGroupType() == true) - { - var queryServerNames = (await GroupProfileManager.GetAllChildEchQuerySni(node)).ToList(); - if (queryServerNames.Count > 0) - { - singboxConfig.dns.rules.Add(new() - { - query_type = [64, 65], - server = Global.SingboxEchDNSTag, - domain = queryServerNames, - }); - } - } - - if (simpleDNSItem.BlockBindingQuery == true) - { - singboxConfig.dns.rules.Add(new() + _coreConfig.dns.rules.Add(new() { query_type = [64, 65], action = "predefined", @@ -264,7 +220,7 @@ public partial class CoreConfigSingboxService }); } - if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == true) + if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == true) { var fakeipFilterRule = JsonUtils.Deserialize(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName)); fakeipFilterRule.invert = true; @@ -284,13 +240,13 @@ public partial class CoreConfigSingboxService ] }; - singboxConfig.dns.rules.Add(rule4Fake); + _coreConfig.dns.rules.Add(rule4Fake); } - var routing = await ConfigHandler.GetDefaultRouting(_config); + var routing = context.RoutingItem; if (routing == null) { - return 0; + return; } var rules = JsonUtils.Deserialize>(routing.RuleSet) ?? []; @@ -298,9 +254,9 @@ public partial class CoreConfigSingboxService var expectedIPsRegions = new List(); var regionNames = new HashSet(); - if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs)) + if (!string.IsNullOrEmpty(simpleDnsItem?.DirectExpectedIPs)) { - var ipItems = simpleDNSItem.DirectExpectedIPs + var ipItems = simpleDnsItem.DirectExpectedIPs .Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()) .Where(s => !string.IsNullOrEmpty(s)) @@ -348,7 +304,7 @@ public partial class CoreConfigSingboxService if (item.OutboundTag == Global.DirectTag) { rule.server = Global.SingboxDirectDNSTag; - rule.strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Freedom); + rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom); if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0) { @@ -373,31 +329,46 @@ public partial class CoreConfigSingboxService } else { - if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false) + if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false) { var rule4Fake = JsonUtils.DeepCopy(rule); rule4Fake.server = Global.SingboxFakeDNSTag; rule4Fake.query_type = new List { 1, 28 }; // A and AAAA rule4Fake.rewrite_ttl = 1; - singboxConfig.dns.rules.Add(rule4Fake); + _coreConfig.dns.rules.Add(rule4Fake); } rule.server = Global.SingboxRemoteDNSTag; - rule.strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Proxy); + rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy); } - singboxConfig.dns.rules.Add(rule); + _coreConfig.dns.rules.Add(rule); } - - return 0; } - private async Task GenDnsCompatible(ProfileItem? node, SingboxConfig singboxConfig) + private void GenMinimizedDns() + { + GenDnsServers(); + foreach (var server in _coreConfig.dns!.servers.Where(s => !string.IsNullOrEmpty(s.detour)).ToList()) + { + _coreConfig.dns.servers.Remove(server); + } + _coreConfig.dns ??= new(); + _coreConfig.dns.rules ??= []; + _coreConfig.dns.rules.Clear(); + _coreConfig.dns.final = Global.SingboxDirectDNSTag; + _coreConfig.route.default_domain_resolver = new() + { + server = Global.SingboxDirectDNSTag, + }; + } + + private void GenDnsCustom() { try { - var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); + var item = context.RawDnsItem; var strDNS = string.Empty; - if (_config.TunModeItem.EnableTun) + if (context.IsTunEnabled) { strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS; } @@ -409,61 +380,33 @@ public partial class CoreConfigSingboxService var dns4Sbox = JsonUtils.Deserialize(strDNS); if (dns4Sbox is null) { - return 0; + return; } - singboxConfig.dns = dns4Sbox; - - if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty()) + _coreConfig.dns = dns4Sbox; + if (dns4Sbox.servers?.Count > 0 && + dns4Sbox.servers.First().address.IsNullOrEmpty()) { - await GenDnsDomainsCompatible(singboxConfig, item); + GenDnsProtectCustom(); } else { - await GenDnsDomainsLegacyCompatible(singboxConfig, item); + GenDnsProtectCustomLegacy(); } - - await GenOutboundDnsRule(node, singboxConfig); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenDnsDomainsCompatible(SingboxConfig singboxConfig, DNSItem? dnsItem) + private void GenDnsProtectCustom() { - var dns4Sbox = singboxConfig.dns ?? new(); + var dnsItem = context.RawDnsItem; + var dns4Sbox = _coreConfig.dns ?? new(); dns4Sbox.servers ??= []; dns4Sbox.rules ??= []; var tag = Global.SingboxLocalDNSTag; - - var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress; - - var localDnsServer = ParseDnsAddress(finalDnsAddress); - localDnsServer.tag = tag; - - dns4Sbox.servers.Add(localDnsServer); - - singboxConfig.dns = dns4Sbox; - return await Task.FromResult(0); - } - - private async Task GenDnsDomainsLegacyCompatible(SingboxConfig singboxConfig, DNSItem? dnsItem) - { - var dns4Sbox = singboxConfig.dns ?? new(); - dns4Sbox.servers ??= []; - dns4Sbox.rules ??= []; - - var tag = Global.SingboxLocalDNSTag; - dns4Sbox.servers.Add(new() - { - tag = tag, - address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress, - detour = Global.DirectTag, - strategy = string.IsNullOrEmpty(dnsItem?.DomainStrategy4Freedom) ? null : dnsItem?.DomainStrategy4Freedom, - }); dns4Sbox.rules.Insert(0, new() { server = tag, @@ -475,56 +418,41 @@ public partial class CoreConfigSingboxService clash_mode = ERuleMode.Global.ToString() }); - var lstDomain = singboxConfig.outbounds - .Where(t => t.server.IsNotEmpty() && Utils.IsDomain(t.server)) - .Select(t => t.server) - .Distinct() - .ToList(); - if (lstDomain != null && lstDomain.Count > 0) - { - dns4Sbox.rules.Insert(0, new() - { - server = tag, - domain = lstDomain - }); - } + var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress; - singboxConfig.dns = dns4Sbox; - return await Task.FromResult(0); + var localDnsServer = ParseDnsAddress(finalDnsAddress); + localDnsServer.tag = tag; + + dns4Sbox.servers.Add(localDnsServer); + dns4Sbox.rules.Insert(0, BuildProtectDomainRule()); + + _coreConfig.dns = dns4Sbox; } - private async Task GenOutboundDnsRule(ProfileItem? node, SingboxConfig singboxConfig) + private void GenDnsProtectCustomLegacy() { - if (node == null) - { - return 0; - } + GenDnsProtectCustom(); - List domain = new(); - if (Utils.IsDomain(node.Address)) // normal outbound + _coreConfig.dns?.servers?.RemoveAll(s => s.tag == Global.SingboxLocalDNSTag); + var dnsItem = context.RawDnsItem; + var localDnsServer = new Server4Sbox() { - domain.Add(node.Address); - } - if (node.Address == Global.Loopback && node.SpiderX.IsNotEmpty()) // Tun2SocksAddress - { - domain.AddRange(Utils.String2List(node.SpiderX) - .Where(Utils.IsDomain) - .Distinct() - .ToList()); - } - if (domain.Count == 0) - { - return 0; - } + address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) + ? Global.DomainPureIPDNSAddress.FirstOrDefault() + : dnsItem?.DomainDNSAddress, + tag = Global.SingboxLocalDNSTag, + detour = Global.DirectTag, + }; + _coreConfig.dns?.servers?.Add(localDnsServer); + } - singboxConfig.dns.rules ??= new List(); - singboxConfig.dns.rules.Insert(0, new Rule4Sbox + private Rule4Sbox BuildProtectDomainRule() + { + return new() { server = Global.SingboxLocalDNSTag, - domain = domain, - }); - - return await Task.FromResult(0); + domain = context.ProtectDomainList.ToList(), + }; } private static Server4Sbox? ParseDnsAddress(string address) diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs index 4e14c68813..ab9a9d9fe8 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs @@ -2,12 +2,12 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task GenInbounds(SingboxConfig singboxConfig) + private void GenInbounds() { try { var listen = "0.0.0.0"; - singboxConfig.inbounds = []; + _coreConfig.inbounds = []; if (!_config.TunModeItem.EnableTun || (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && AppManager.Instance.RunningCoreType == ECoreType.sing_box)) @@ -18,23 +18,23 @@ public partial class CoreConfigSingboxService tag = EInboundProtocol.socks.ToString(), listen = Global.Loopback, }; - singboxConfig.inbounds.Add(inbound); + _coreConfig.inbounds.Add(inbound); inbound.listen_port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); if (_config.Inbound.First().SecondLocalPortEnabled) { - var inbound2 = GetInbound(inbound, EInboundProtocol.socks2, true); - singboxConfig.inbounds.Add(inbound2); + var inbound2 = BuildInbound(inbound, EInboundProtocol.socks2, true); + _coreConfig.inbounds.Add(inbound2); } if (_config.Inbound.First().AllowLANConn) { if (_config.Inbound.First().NewPort4LAN) { - var inbound3 = GetInbound(inbound, EInboundProtocol.socks3, true); + var inbound3 = BuildInbound(inbound, EInboundProtocol.socks3, true); inbound3.listen = listen; - singboxConfig.inbounds.Add(inbound3); + _coreConfig.inbounds.Add(inbound3); //auth if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) @@ -71,17 +71,16 @@ public partial class CoreConfigSingboxService tunInbound.address = ["172.18.0.1/30"]; } - singboxConfig.inbounds.Add(tunInbound); + _coreConfig.inbounds.Add(tunInbound); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks) + private Inbound4Sbox BuildInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks) { var inbound = JsonUtils.DeepCopy(inItem); inbound.tag = protocol.ToString(); diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxLogService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxLogService.cs index 59e65471ac..b438242c30 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxLogService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxLogService.cs @@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task GenLog(SingboxConfig singboxConfig) + private void GenLog() { try { @@ -11,11 +11,11 @@ public partial class CoreConfigSingboxService case "debug": case "info": case "error": - singboxConfig.log.level = _config.CoreBasicItem.Loglevel; + _coreConfig.log.level = _config.CoreBasicItem.Loglevel; break; case "warning": - singboxConfig.log.level = "warn"; + _coreConfig.log.level = "warn"; break; default: @@ -23,18 +23,17 @@ public partial class CoreConfigSingboxService } if (_config.CoreBasicItem.Loglevel == Global.None) { - singboxConfig.log.disabled = true; + _coreConfig.log.disabled = true; } if (_config.CoreBasicItem.LogEnabled) { var dtNow = DateTime.Now; - singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt"); + _coreConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt"); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index 178350b3ee..23b331f635 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -2,20 +2,93 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task GenOutbound(ProfileItem node, Outbound4Sbox outbound) + private void GenOutbounds() + { + var proxyOutbounds = BuildAllProxyOutbounds(); + FillRangeProxy(proxyOutbounds, _coreConfig, true); + } + + private List BuildAllProxyOutbounds(string baseTagName = Global.ProxyTag, bool withSelector = true) + { + var proxyOutboundList = new List(); + if (!_node.ConfigType.IsComplexType()) + { + var outbound = BuildProxyOutbound(baseTagName); + proxyOutboundList.Add(outbound); + } + else + { + proxyOutboundList.AddRange(BuildGroupProxyOutbounds(baseTagName)); + } + var proxyTags = proxyOutboundList.Where(n => n.tag.StartsWith(Global.ProxyTag)).Select(n => n.tag).ToList(); + if (proxyTags.Count > 1) + { + proxyOutboundList.InsertRange(0, BuildSelectorOutbounds(proxyTags, baseTagName)); + } + return proxyOutboundList; + } + + private BaseServer4Sbox BuildProxyOutbound(string baseTagName = Global.ProxyTag) + { + var outbound = BuildProxyServer(); + outbound.tag = baseTagName; + return outbound; + } + + private List BuildGroupProxyOutbounds(string baseTagName = Global.ProxyTag) + { + var proxyOutboundList = new List(); + switch (_node.ConfigType) + { + case EConfigType.PolicyGroup: + proxyOutboundList = BuildOutboundsList(baseTagName); + break; + case EConfigType.ProxyChain: + proxyOutboundList = BuildChainOutboundsList(baseTagName); + break; + } + return proxyOutboundList; + } + + private BaseServer4Sbox BuildProxyServer() { try { - var protocolExtra = node.GetProtocolExtra(); - outbound.server = node.Address; - outbound.server_port = node.Port; - outbound.type = Global.ProtocolTypes[node.ConfigType]; + var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); + if (_node.ConfigType == EConfigType.WireGuard) + { + var endpoint = JsonUtils.Deserialize(txtOutbound); + FillEndpoint(endpoint); + return endpoint; + } + else + { + var outbound = JsonUtils.Deserialize(txtOutbound); + FillOutbound(outbound); + return outbound; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + throw new InvalidOperationException(); + } - switch (node.ConfigType) + private void FillOutbound(Outbound4Sbox outbound) + { + try + { + var protocolExtra = _node.GetProtocolExtra(); + outbound.server = _node.Address; + outbound.server_port = _node.Port; + outbound.type = Global.ProtocolTypes[_node.ConfigType]; + + switch (_node.ConfigType) { case EConfigType.VMess: { - outbound.uuid = node.Password; + outbound.uuid = _node.Password; outbound.alter_id = int.TryParse(protocolExtra.AlterId, out var result) ? result : 0; if (Global.VmessSecurities.Contains(protocolExtra.VmessSecurity)) { @@ -26,41 +99,41 @@ public partial class CoreConfigSingboxService outbound.security = Global.DefaultSecurity; } - await GenOutboundMux(node, outbound); - await GenOutboundTransport(node, outbound); + FillOutboundMux(outbound); + FillOutboundTransport(outbound); break; } case EConfigType.Shadowsocks: { - outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod) + outbound.method = AppManager.Instance.GetShadowsocksSecurities(_node).Contains(protocolExtra.SsMethod) ? protocolExtra.SsMethod : Global.None; - outbound.password = node.Password; + outbound.password = _node.Password; - if (node.Network == nameof(ETransport.tcp) && node.HeaderType == Global.TcpHeaderHttp) + if (_node.Network == nameof(ETransport.tcp) && _node.HeaderType == Global.TcpHeaderHttp) { outbound.plugin = "obfs-local"; - outbound.plugin_opts = $"obfs=http;obfs-host={node.RequestHost};"; + outbound.plugin_opts = $"obfs=http;obfs-host={_node.RequestHost};"; } else { var pluginArgs = string.Empty; - if (node.Network == nameof(ETransport.ws)) + if (_node.Network == nameof(ETransport.ws)) { pluginArgs += "mode=websocket;"; - pluginArgs += $"host={node.RequestHost};"; + pluginArgs += $"host={_node.RequestHost};"; // https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172 // Equal signs and commas [and backslashes] must be escaped with a backslash. - var path = node.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,"); + var path = _node.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,"); pluginArgs += $"path={path};"; } - else if (node.Network == nameof(ETransport.quic)) + else if (_node.Network == nameof(ETransport.quic)) { pluginArgs += "mode=quic;"; } - if (node.StreamSecurity == Global.StreamSecurity) + if (_node.StreamSecurity == Global.StreamSecurity) { pluginArgs += "tls;"; - var certs = CertPemManager.ParsePemChain(node.Cert); + var certs = CertPemManager.ParsePemChain(_node.Cert); if (certs.Count > 0) { var cert = certs.First(); @@ -84,33 +157,33 @@ public partial class CoreConfigSingboxService } } - await GenOutboundMux(node, outbound); + FillOutboundMux(outbound); break; } case EConfigType.SOCKS: { outbound.version = "5"; - if (node.Username.IsNotEmpty() - && node.Password.IsNotEmpty()) + if (_node.Username.IsNotEmpty() + && _node.Password.IsNotEmpty()) { - outbound.username = node.Username; - outbound.password = node.Password; + outbound.username = _node.Username; + outbound.password = _node.Password; } break; } case EConfigType.HTTP: { - if (node.Username.IsNotEmpty() - && node.Password.IsNotEmpty()) + if (_node.Username.IsNotEmpty() + && _node.Password.IsNotEmpty()) { - outbound.username = node.Username; - outbound.password = node.Password; + outbound.username = _node.Username; + outbound.password = _node.Password; } break; } case EConfigType.VLESS: { - outbound.uuid = node.Password; + outbound.uuid = _node.Password; outbound.packet_encoding = "xudp"; @@ -120,23 +193,23 @@ public partial class CoreConfigSingboxService } else { - await GenOutboundMux(node, outbound); + FillOutboundMux(outbound); } - await GenOutboundTransport(node, outbound); + FillOutboundTransport(outbound); break; } case EConfigType.Trojan: { - outbound.password = node.Password; + outbound.password = _node.Password; - await GenOutboundMux(node, outbound); - await GenOutboundTransport(node, outbound); + FillOutboundMux(outbound); + FillOutboundTransport(outbound); break; } case EConfigType.Hysteria2: { - outbound.password = node.Password; + outbound.password = _node.Password; if (!protocolExtra.SalamanderPass.IsNullOrEmpty()) { @@ -190,37 +263,36 @@ public partial class CoreConfigSingboxService } case EConfigType.TUIC: { - outbound.uuid = node.Username; - outbound.password = node.Password; - outbound.congestion_control = node.HeaderType; + outbound.uuid = _node.Username; + outbound.password = _node.Password; + outbound.congestion_control = _node.HeaderType; break; } case EConfigType.Anytls: { - outbound.password = node.Password; + outbound.password = _node.Password; break; } } - await GenOutboundTls(node, outbound); + FillOutboundTls(outbound); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenEndpoint(ProfileItem node, Endpoints4Sbox endpoint) + private void FillEndpoint(Endpoints4Sbox endpoint) { try { - var protocolExtra = node.GetProtocolExtra(); + var protocolExtra = _node.GetProtocolExtra(); endpoint.address = Utils.String2List(protocolExtra.WgInterfaceAddress); - endpoint.type = Global.ProtocolTypes[node.ConfigType]; + endpoint.type = Global.ProtocolTypes[_node.ConfigType]; - switch (node.ConfigType) + switch (_node.ConfigType) { case EConfigType.WireGuard: { @@ -229,12 +301,12 @@ public partial class CoreConfigSingboxService public_key = protocolExtra.WgPublicKey, pre_shared_key = protocolExtra.WgPresharedKey, reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(), - address = node.Address, - port = node.Port, + address = _node.Address, + port = _node.Port, // TODO default ["0.0.0.0/0", "::/0"] allowed_ips = new() { "0.0.0.0/0", "::/0" }, }; - endpoint.private_key = node.Password; + endpoint.private_key = _node.Password; endpoint.mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(); endpoint.peers = [peer]; break; @@ -245,47 +317,13 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenServer(ProfileItem node) + private void FillOutboundMux(Outbound4Sbox outbound) { try { - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - if (node.ConfigType == EConfigType.WireGuard) - { - var endpoint = JsonUtils.Deserialize(txtOutbound); - var ret = await GenEndpoint(node, endpoint); - if (ret != 0) - { - return null; - } - return endpoint; - } - else - { - var outbound = JsonUtils.Deserialize(txtOutbound); - var ret = await GenOutbound(node, outbound); - if (ret != 0) - { - return null; - } - return outbound; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return await Task.FromResult(null); - } - - private async Task GenOutboundMux(ProfileItem node, Outbound4Sbox outbound) - { - try - { - var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; + var muxEnabled = _node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; if (muxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty()) { var mux = new Multiplex4Sbox() @@ -302,66 +340,65 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenOutboundTls(ProfileItem node, Outbound4Sbox outbound) + private void FillOutboundTls(Outbound4Sbox outbound) { try { - if (node.StreamSecurity is not (Global.StreamSecurityReality or Global.StreamSecurity)) + if (_node.StreamSecurity is not (Global.StreamSecurityReality or Global.StreamSecurity)) { - return await Task.FromResult(0); + return; } - if (node.ConfigType is EConfigType.Shadowsocks or EConfigType.SOCKS or EConfigType.WireGuard) + if (_node.ConfigType is EConfigType.Shadowsocks or EConfigType.SOCKS or EConfigType.WireGuard) { - return await Task.FromResult(0); + return; } - var server_name = string.Empty; - if (node.Sni.IsNotEmpty()) + var serverName = string.Empty; + if (_node.Sni.IsNotEmpty()) { - server_name = node.Sni; + serverName = _node.Sni; } - else if (node.RequestHost.IsNotEmpty()) + else if (_node.RequestHost.IsNotEmpty()) { - server_name = Utils.String2List(node.RequestHost)?.First(); + serverName = Utils.String2List(_node.RequestHost)?.First(); } var tls = new Tls4Sbox() { enabled = true, record_fragment = _config.CoreBasicItem.EnableFragment ? true : null, - server_name = server_name, - insecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure), - alpn = node.GetAlpn(), + server_name = serverName, + insecure = Utils.ToBool(_node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : _node.AllowInsecure), + alpn = _node.GetAlpn(), }; - if (node.Fingerprint.IsNotEmpty()) + if (_node.Fingerprint.IsNotEmpty()) { tls.utls = new Utls4Sbox() { enabled = true, - fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint + fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint }; } - if (node.StreamSecurity == Global.StreamSecurity) + if (_node.StreamSecurity == Global.StreamSecurity) { - var certs = CertPemManager.ParsePemChain(node.Cert); + var certs = CertPemManager.ParsePemChain(_node.Cert); if (certs.Count > 0) { tls.certificate = certs; tls.insecure = false; } } - else if (node.StreamSecurity == Global.StreamSecurityReality) + else if (_node.StreamSecurity == Global.StreamSecurityReality) { tls.reality = new Reality4Sbox() { enabled = true, - public_key = node.PublicKey, - short_id = node.ShortId + public_key = _node.PublicKey, + short_id = _node.ShortId }; tls.insecure = false; } - var (ech, _) = ParseEchParam(node.EchConfigList); + var (ech, _) = ParseEchParam(_node.EchConfigList); if (ech is not null) { tls.ech = ech; @@ -372,35 +409,34 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenOutboundTransport(ProfileItem node, Outbound4Sbox outbound) + private void FillOutboundTransport(Outbound4Sbox outbound) { try { var transport = new Transport4Sbox(); - switch (node.GetNetwork()) + switch (_node.GetNetwork()) { case nameof(ETransport.h2): transport.type = nameof(ETransport.http); - transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost); - transport.path = node.Path.NullIfEmpty(); + transport.host = _node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(_node.RequestHost); + transport.path = _node.Path.NullIfEmpty(); break; case nameof(ETransport.tcp): //http - if (node.HeaderType == Global.TcpHeaderHttp) + if (_node.HeaderType == Global.TcpHeaderHttp) { transport.type = nameof(ETransport.http); - transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost); - transport.path = node.Path.NullIfEmpty(); + transport.host = _node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(_node.RequestHost); + transport.path = _node.Path.NullIfEmpty(); } break; case nameof(ETransport.ws): transport.type = nameof(ETransport.ws); - var wsPath = node.Path; + var wsPath = _node.Path; // Parse eh and ed parameters from path using regex if (!wsPath.IsNullOrEmpty()) @@ -429,19 +465,19 @@ public partial class CoreConfigSingboxService } transport.path = wsPath.NullIfEmpty(); - if (node.RequestHost.IsNotEmpty()) + if (_node.RequestHost.IsNotEmpty()) { transport.headers = new() { - Host = node.RequestHost + Host = _node.RequestHost }; } break; case nameof(ETransport.httpupgrade): transport.type = nameof(ETransport.httpupgrade); - transport.path = node.Path.NullIfEmpty(); - transport.host = node.RequestHost.NullIfEmpty(); + transport.path = _node.Path.NullIfEmpty(); + transport.host = _node.RequestHost.NullIfEmpty(); break; @@ -451,7 +487,7 @@ public partial class CoreConfigSingboxService case nameof(ETransport.grpc): transport.type = nameof(ETransport.grpc); - transport.service_name = node.Path; + transport.service_name = _node.Path; transport.idle_timeout = _config.GrpcItem.IdleTimeout?.ToString("##s"); transport.ping_timeout = _config.GrpcItem.HealthCheckTimeout?.ToString("##s"); transport.permit_without_stream = _config.GrpcItem.PermitWithoutStream; @@ -469,458 +505,190 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenGroupOutbound(ProfileItem node, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false) + private List BuildSelectorOutbounds(List proxyTags, string baseTagName = Global.ProxyTag) { - try + var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing; + var outUrltest = new Outbound4Sbox { - if (!node.ConfigType.IsGroupType()) - { - return -1; - } - var hasCycle = await GroupProfileManager.HasCycle(node); - if (hasCycle) - { - return -1; - } + type = "urltest", + tag = $"{baseTagName}-auto", + outbounds = proxyTags, + interrupt_exist_connections = false, + }; - var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node); - if (childProfiles.Count <= 0) - { - return -1; - } - switch (node.ConfigType) - { - case EConfigType.PolicyGroup: - var multipleLoad = profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing; - if (ignoreOriginChain) - { - await GenOutboundsList(childProfiles, singboxConfig, multipleLoad, baseTagName); - } - else - { - await GenOutboundsListWithChain(childProfiles, singboxConfig, multipleLoad, baseTagName); - } - - break; - - case EConfigType.ProxyChain: - await GenChainOutboundsList(childProfiles, singboxConfig, baseTagName); - break; - - default: - break; - } - } - catch (Exception ex) + if (multipleLoad == EMultipleLoad.Fallback) { - Logging.SaveLog(_tag, ex); + outUrltest.tolerance = 5000; } - return await Task.FromResult(0); + + // Add selector outbound (manual selection) + var outSelector = new Outbound4Sbox + { + type = "selector", + tag = baseTagName, + outbounds = JsonUtils.DeepCopy(proxyTags), + interrupt_exist_connections = false, + }; + outSelector.outbounds.Insert(0, outUrltest.tag); + + return [outSelector, outUrltest]; } - private async Task GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig) + private List BuildOutboundsList(string baseTagName = Global.ProxyTag) { - if (node.Subid.IsNullOrEmpty()) + var nodes = new List(); + foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? []) { - return 0; - } - try - { - var subItem = await AppManager.Instance.GetSubItem(node.Subid); - if (subItem is null) + if (context.AllProxiesMap.TryGetValue(nodeId, out var node)) { - return 0; - } - - //current proxy - BaseServer4Sbox? outbound = singboxConfig.endpoints?.FirstOrDefault(t => t.tag == Global.ProxyTag, null); - outbound ??= singboxConfig.outbounds.First(); - - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - - //Previous proxy - var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - string? prevOutboundTag = null; - if (prevNode is not null - && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)) - { - prevOutboundTag = $"prev-{Global.ProxyTag}"; - var prevServer = await GenServer(prevNode); - prevServer.tag = prevOutboundTag; - if (prevServer is Endpoints4Sbox endpoint) - { - singboxConfig.endpoints ??= new(); - singboxConfig.endpoints.Add(endpoint); - } - else if (prevServer is Outbound4Sbox outboundPrev) - { - singboxConfig.outbounds.Add(outboundPrev); - } - } - var nextServer = await GenChainOutbounds(subItem, outbound, prevOutboundTag); - - if (nextServer is not null) - { - if (nextServer is Endpoints4Sbox endpoint) - { - singboxConfig.endpoints ??= new(); - singboxConfig.endpoints.Insert(0, endpoint); - } - else if (nextServer is Outbound4Sbox outboundNext) - { - singboxConfig.outbounds.Insert(0, outboundNext); - } + nodes.Add(node); } } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return 0; - } - - private async Task GenOutboundsListWithChain(List nodes, SingboxConfig singboxConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag) - { - try - { - // Get outbound template and initialize lists - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - if (txtOutbound.IsNullOrEmpty()) - { - return 0; - } - - var resultOutbounds = new List(); - var resultEndpoints = new List(); // For endpoints - var prevOutbounds = new List(); // Separate list for prev outbounds - var prevEndpoints = new List(); // Separate list for prev endpoints - var proxyTags = new List(); // For selector and urltest outbounds - - // Cache for chain proxies to avoid duplicate generation - var nextProxyCache = new Dictionary(); - var prevProxyTags = new Dictionary(); // Map from profile name to tag - var prevIndex = 0; // Index for prev outbounds - - // Process each node - var index = 0; - foreach (var node in nodes) - { - index++; - - if (node.ConfigType.IsGroupType()) - { - var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node); - if (childProfiles.Count <= 0) - { - continue; - } - var childBaseTagName = $"{baseTagName}-{index}"; - var ret = node.ConfigType switch - { - EConfigType.PolicyGroup => - await GenOutboundsListWithChain(childProfiles, singboxConfig, - profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing, childBaseTagName), - EConfigType.ProxyChain => - await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName), - _ => throw new NotImplementedException() - }; - if (ret == 0) - { - proxyTags.Add(childBaseTagName); - } - continue; - } - - // Handle proxy chain - string? prevTag = null; - var currentServer = await GenServer(node); - var nextServer = nextProxyCache.GetValueOrDefault(node.Subid, null); - if (nextServer != null) - { - nextServer = JsonUtils.DeepCopy(nextServer); - } - - var subItem = await AppManager.Instance.GetSubItem(node.Subid); - - // current proxy - currentServer.tag = $"{baseTagName}-{index}"; - proxyTags.Add(currentServer.tag); - - if (!node.Subid.IsNullOrEmpty()) - { - if (prevProxyTags.TryGetValue(node.Subid, out var value)) - { - prevTag = value; // maybe null - } - else - { - var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - if (prevNode is not null - && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)) - { - var prevOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(prevNode, prevOutbound); - prevTag = $"prev-{baseTagName}-{++prevIndex}"; - prevOutbound.tag = prevTag; - prevOutbounds.Add(prevOutbound); - } - prevProxyTags[node.Subid] = prevTag; - } - - nextServer = await GenChainOutbounds(subItem, currentServer, prevTag, nextServer); - if (!nextProxyCache.ContainsKey(node.Subid)) - { - nextProxyCache[node.Subid] = nextServer; - } - } - - if (nextServer is not null) - { - if (nextServer is Endpoints4Sbox nextEndpoint) - { - resultEndpoints.Add(nextEndpoint); - } - else if (nextServer is Outbound4Sbox nextOutbound) - { - resultOutbounds.Add(nextOutbound); - } - } - if (currentServer is Endpoints4Sbox currentEndpoint) - { - resultEndpoints.Add(currentEndpoint); - } - else if (currentServer is Outbound4Sbox currentOutbound) - { - resultOutbounds.Add(currentOutbound); - } - } - - // Add urltest outbound (auto selection based on latency) - if (proxyTags.Count > 0) - { - var outUrltest = new Outbound4Sbox - { - type = "urltest", - tag = $"{baseTagName}-auto", - outbounds = proxyTags, - interrupt_exist_connections = false, - }; - - if (multipleLoad == EMultipleLoad.Fallback) - { - outUrltest.tolerance = 5000; - } - - // Add selector outbound (manual selection) - var outSelector = new Outbound4Sbox - { - type = "selector", - tag = baseTagName, - outbounds = JsonUtils.DeepCopy(proxyTags), - interrupt_exist_connections = false, - }; - outSelector.outbounds.Insert(0, outUrltest.tag); - - // Insert these at the beginning - resultOutbounds.Insert(0, outUrltest); - resultOutbounds.Insert(0, outSelector); - } - - // Merge results: first the selector/urltest/proxies, then other outbounds, and finally prev outbounds - var serverList = new List(); - serverList = serverList.Concat(prevOutbounds) - .Concat(resultOutbounds) - .Concat(resultEndpoints) - .ToList(); - await AddRangeOutbounds(serverList, singboxConfig, baseTagName == Global.ProxyTag); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return 0; - } - - private async Task GenChainOutbounds(SubItem subItem, BaseServer4Sbox outbound, string? prevOutboundTag, BaseServer4Sbox? nextOutbound = null) - { - try - { - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - - if (!prevOutboundTag.IsNullOrEmpty()) - { - outbound.detour = prevOutboundTag; - } - - // Next proxy - var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode is not null - && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType)) - { - nextOutbound ??= await GenServer(nextNode); - nextOutbound.tag = outbound.tag; - - outbound.tag = $"mid-{outbound.tag}"; - nextOutbound.detour = outbound.tag; - } - return nextOutbound; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return null; - } - - private async Task GenOutboundsList(List nodes, SingboxConfig singboxConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag) - { - var resultOutbounds = new List(); - var resultEndpoints = new List(); // For endpoints - var proxyTags = new List(); // For selector and urltest outbounds + var resultOutbounds = new List(); for (var i = 0; i < nodes.Count; i++) { var node = nodes[i]; - if (node == null) - { - continue; - } + var currentTag = $"{baseTagName}-{i + 1}"; if (node.ConfigType.IsGroupType()) { - var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node); - if (childProfiles.Count <= 0) - { - continue; - } - var childBaseTagName = $"{baseTagName}-{i + 1}"; - var ret = node.ConfigType switch - { - EConfigType.PolicyGroup => - await GenOutboundsList(childProfiles, singboxConfig, profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing, childBaseTagName), - EConfigType.ProxyChain => - await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName), - _ => throw new NotImplementedException() - }; - if (ret == 0) - { - proxyTags.Add(childBaseTagName); - } + var childProfiles = new CoreConfigSingboxService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag); + resultOutbounds.AddRange(childProfiles); continue; } - var server = await GenServer(node); - if (server is null) - { - break; - } - server.tag = baseTagName + (i + 1).ToString(); - if (server is Endpoints4Sbox endpoint) - { - resultEndpoints.Add(endpoint); - } - else if (server is Outbound4Sbox outbound) - { - resultOutbounds.Add(outbound); - } - proxyTags.Add(server.tag); + var outbound = new CoreConfigSingboxService(context with { Node = node, }).BuildProxyOutbound(); + outbound.tag = currentTag; + resultOutbounds.Add(outbound); } - // Add urltest outbound (auto selection based on latency) - if (proxyTags.Count > 0) - { - var outUrltest = new Outbound4Sbox - { - type = "urltest", - tag = $"{baseTagName}-auto", - outbounds = proxyTags, - interrupt_exist_connections = false, - }; - if (multipleLoad == EMultipleLoad.Fallback) - { - outUrltest.tolerance = 5000; - } - // Add selector outbound (manual selection) - var outSelector = new Outbound4Sbox - { - type = "selector", - tag = baseTagName, - outbounds = JsonUtils.DeepCopy(proxyTags), - interrupt_exist_connections = false, - }; - outSelector.outbounds.Insert(0, outUrltest.tag); - // Insert these at the beginning - resultOutbounds.Insert(0, outUrltest); - resultOutbounds.Insert(0, outSelector); - } - var serverList = new List(); - serverList = serverList.Concat(resultOutbounds) - .Concat(resultEndpoints) - .ToList(); - await AddRangeOutbounds(serverList, singboxConfig, baseTagName == Global.ProxyTag); - return await Task.FromResult(0); + return resultOutbounds; } - private async Task GenChainOutboundsList(List nodes, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag) + private List BuildChainOutboundsList(string baseTagName = Global.ProxyTag) { + var nodes = new List(); + foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? []) + { + if (context.AllProxiesMap.TryGetValue(nodeId, out var node)) + { + nodes.Add(node); + } + } // Based on actual network flow instead of data packets var nodesReverse = nodes.AsEnumerable().Reverse().ToList(); - var resultOutbounds = new List(); - var resultEndpoints = new List(); // For endpoints + var resultOutbounds = new List(); for (var i = 0; i < nodesReverse.Count; i++) { var node = nodesReverse[i]; - var server = await GenServer(node); - - if (server is null) + var currentTag = i == 0 ? baseTagName : $"chain-{baseTagName}-{i}"; + var dialerProxyTag = i != nodesReverse.Count - 1 ? $"chain-{baseTagName}-{i + 1}" : null; + if (node.ConfigType.IsGroupType()) { - break; + var childProfiles = new CoreConfigSingboxService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag); + if (!dialerProxyTag.IsNullOrEmpty()) + { + var chainEndNodes = + childProfiles.Where(n => n?.detour.IsNullOrEmpty() ?? true); + foreach (var chainEndNode in chainEndNodes) + { + chainEndNode.detour = dialerProxyTag; + } + } + if (i != 0) + { + var chainStartNodes = childProfiles.Where(n => n.tag.StartsWith(currentTag)).ToList(); + if (chainStartNodes.Count == 1) + { + foreach (var existedChainEndNode in resultOutbounds.Where(n => n.detour == currentTag)) + { + existedChainEndNode.detour = chainStartNodes.First().tag; + } + } + else if (chainStartNodes.Count > 1) + { + var existedChainNodes = CloneOutbounds(resultOutbounds); + resultOutbounds.Clear(); + var j = 0; + foreach (var chainStartNode in chainStartNodes) + { + var existedChainNodesClone = CloneOutbounds(existedChainNodes); + foreach (var existedChainNode in existedChainNodesClone) + { + var cloneTag = $"{existedChainNode.tag}-clone-{j + 1}"; + existedChainNode.tag = cloneTag; + } + for (var k = 0; k < existedChainNodesClone.Count; k++) + { + var existedChainNode = existedChainNodesClone[k]; + var previousDialerProxyTag = existedChainNode.detour; + var nextTag = k + 1 < existedChainNodesClone.Count + ? existedChainNodesClone[k + 1].tag + : chainStartNode.tag; + existedChainNode.detour = (previousDialerProxyTag == currentTag) + ? chainStartNode.tag + : nextTag; + resultOutbounds.Add(existedChainNode); + } + j++; + } + } + } + resultOutbounds.AddRange(childProfiles); + continue; + } + var outbound = new CoreConfigSingboxService(context with { Node = node, }).BuildProxyOutbound(); + + outbound.tag = currentTag; + + if (!dialerProxyTag.IsNullOrEmpty()) + { + outbound.detour = dialerProxyTag; } - if (i == 0) - { - server.tag = baseTagName; - } - else - { - server.tag = baseTagName + i.ToString(); - } - - if (i != nodesReverse.Count - 1) - { - server.detour = baseTagName + (i + 1).ToString(); - } - - if (server is Endpoints4Sbox endpoint) - { - resultEndpoints.Add(endpoint); - } - else if (server is Outbound4Sbox outbound) - { - resultOutbounds.Add(outbound); - } + resultOutbounds.Add(outbound); } - var serverList = new List(); - serverList = serverList.Concat(resultOutbounds) - .Concat(resultEndpoints) - .ToList(); - await AddRangeOutbounds(serverList, singboxConfig, baseTagName == Global.ProxyTag); - return await Task.FromResult(0); + return resultOutbounds; } - private async Task AddRangeOutbounds(List servers, SingboxConfig singboxConfig, bool prepend = true) + private static List CloneOutbounds(List source) + { + if (source is null || source.Count == 0) + { + return []; + } + + var result = new List(source.Count); + foreach (var item in source) + { + BaseServer4Sbox? clone = null; + if (item is Outbound4Sbox outbound) + { + clone = JsonUtils.DeepCopy(outbound); + } + else if (item is Endpoints4Sbox endpoint) + { + clone = JsonUtils.DeepCopy(endpoint); + } + if (clone is not null) + { + result.Add(clone); + } + } + return result; + } + + private static void FillRangeProxy(List servers, SingboxConfig singboxConfig, bool prepend = true) { try { if (servers is null || servers.Count <= 0) { - return 0; + return; } var outbounds = servers.Where(s => s is Outbound4Sbox).Cast().ToList(); var endpoints = servers.Where(s => s is Endpoints4Sbox).Cast().ToList(); - singboxConfig.endpoints ??= new(); + singboxConfig.endpoints ??= []; if (prepend) { singboxConfig.outbounds.InsertRange(0, outbounds); @@ -936,10 +704,9 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private (Ech4Sbox? ech, Server4Sbox? dnsServer) ParseEchParam(string? echConfig) + private static (Ech4Sbox? ech, Server4Sbox? dnsServer) ParseEchParam(string? echConfig) { if (echConfig.IsNullOrEmpty()) { diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs index 6198d0277f..9bcb9adf24 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs @@ -2,23 +2,23 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task GenRouting(SingboxConfig singboxConfig) + private void GenRouting() { try { - singboxConfig.route.final = Global.ProxyTag; - var simpleDnsItem = _config.SimpleDNSItem; + _coreConfig.route.final = Global.ProxyTag; + var simpleDnsItem = context.SimpleDnsItem; var defaultDomainResolverTag = Global.SingboxDirectDNSTag; var directDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom); - var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); + var rawDNSItem = context.RawDnsItem; if (rawDNSItem is { Enabled: true }) { defaultDomainResolverTag = Global.SingboxLocalDNSTag; directDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom; } - singboxConfig.route.default_domain_resolver = new() + _coreConfig.route.default_domain_resolver = new() { server = defaultDomainResolverTag, strategy = directDnsStrategy @@ -26,23 +26,23 @@ public partial class CoreConfigSingboxService if (_config.TunModeItem.EnableTun) { - singboxConfig.route.auto_detect_interface = true; + _coreConfig.route.auto_detect_interface = true; var tunRules = JsonUtils.Deserialize>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName)); if (tunRules != null) { - singboxConfig.route.rules.AddRange(tunRules); + _coreConfig.route.rules.AddRange(tunRules); } - GenRoutingDirectExe(out var lstDnsExe, out var lstDirectExe); - singboxConfig.route.rules.Add(new() + var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe(); + _coreConfig.route.rules.Add(new() { - port = new() { 53 }, + port = [53], action = "hijack-dns", process_name = lstDnsExe }); - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { outbound = Global.DirectTag, process_name = lstDirectExe @@ -51,66 +51,62 @@ public partial class CoreConfigSingboxService if (_config.Inbound.First().SniffingEnabled) { - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { action = "sniff" }); - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { - protocol = new() { "dns" }, + protocol = ["dns"], action = "hijack-dns" }); } else { - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { - port = new() { 53 }, - network = new() { "udp" }, + port = [53], + network = ["udp"], action = "hijack-dns" }); } var hostsDomains = new List(); - var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); - if (dnsItem == null || !dnsItem.Enabled) + if (rawDNSItem is not { Enabled: true }) { var userHostsMap = Utils.ParseHostsToDictionary(simpleDnsItem.Hosts); hostsDomains.AddRange(userHostsMap.Select(kvp => kvp.Key)); if (simpleDnsItem.UseSystemHosts == true) { var systemHostsMap = Utils.GetSystemHosts(); - foreach (var kvp in systemHostsMap) - { - hostsDomains.Add(kvp.Key); - } + hostsDomains.AddRange(systemHostsMap.Select(kvp => kvp.Key)); } } if (hostsDomains.Count > 0) { - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { action = "resolve", domain = hostsDomains, }); } - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { outbound = Global.DirectTag, clash_mode = ERuleMode.Direct.ToString() }); - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { outbound = Global.ProxyTag, clash_mode = ERuleMode.Global.ToString() }); var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty(); - var defaultRouting = await ConfigHandler.GetDefaultRouting(_config); - if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty()) + var routing = context.RoutingItem; + if (routing.DomainStrategy4Singbox.IsNotEmpty()) { - domainStrategy = defaultRouting.DomainStrategy4Singbox; + domainStrategy = routing.DomainStrategy4Singbox; } var resolveRule = new Rule4Sbox { @@ -119,10 +115,9 @@ public partial class CoreConfigSingboxService }; if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand) { - singboxConfig.route.rules.Add(resolveRule); + _coreConfig.route.rules.Add(resolveRule); } - var routing = await ConfigHandler.GetDefaultRouting(_config); var ipRules = new List(); if (routing != null) { @@ -139,7 +134,7 @@ public partial class CoreConfigSingboxService continue; } - await GenRoutingUserRule(item1, singboxConfig); + GenRoutingUserRule(item1); if (item1.Ip?.Count > 0) { @@ -149,10 +144,10 @@ public partial class CoreConfigSingboxService } if (_config.RoutingBasicItem.DomainStrategy == Global.IPIfNonMatch) { - singboxConfig.route.rules.Add(resolveRule); + _coreConfig.route.rules.Add(resolveRule); foreach (var item2 in ipRules) { - await GenRoutingUserRule(item2, singboxConfig); + GenRoutingUserRule(item2); } } } @@ -160,10 +155,9 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return 0; } - private void GenRoutingDirectExe(out List lstDnsExe, out List lstDirectExe) + private static (List lstDnsExe, List lstDirectExe) BuildRoutingDirectExe() { var dnsExeSet = new HashSet(StringComparer.OrdinalIgnoreCase); var directExeSet = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -187,20 +181,22 @@ public partial class CoreConfigSingboxService } } - lstDnsExe = new List(dnsExeSet); - lstDirectExe = new List(directExeSet); + var lstDnsExe = new List(dnsExeSet); + var lstDirectExe = new List(directExeSet); + + return (lstDnsExe, lstDirectExe); } - private async Task GenRoutingUserRule(RulesItem item, SingboxConfig singboxConfig) + private void GenRoutingUserRule(RulesItem? item) { try { if (item == null) { - return 0; + return; } - item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig); - var rules = singboxConfig.route.rules; + item.OutboundTag = GenRoutingUserRuleOutbound(item.OutboundTag ?? Global.ProxyTag); + var rules = _coreConfig.route.rules; var rule = new Rule4Sbox(); if (item.OutboundTag == "block") @@ -326,10 +322,9 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private bool ParseV2Domain(string domain, Rule4Sbox rule) + private static bool ParseV2Domain(string domain, Rule4Sbox rule) { if (domain.StartsWith('#') || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:")) { @@ -368,7 +363,7 @@ public partial class CoreConfigSingboxService return true; } - private bool ParseV2Address(string address, Rule4Sbox rule) + private static bool ParseV2Address(string address, Rule4Sbox rule) { if (address.StartsWith("ext:") || address.StartsWith("ext-ip:")) { @@ -401,14 +396,14 @@ public partial class CoreConfigSingboxService return true; } - private async Task GenRoutingUserRuleOutbound(string outboundTag, SingboxConfig singboxConfig) + private string GenRoutingUserRuleOutbound(string outboundTag) { if (Global.OutboundTags.Contains(outboundTag)) { return outboundTag; } - var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag); + var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}"); if (node == null || (!Global.SingboxSupportConfigType.Contains(node.ConfigType) @@ -418,39 +413,15 @@ public partial class CoreConfigSingboxService } var tag = $"{node.IndexId}-{Global.ProxyTag}"; - if (singboxConfig.outbounds.Any(o => o.tag == tag) - || (singboxConfig.endpoints != null && singboxConfig.endpoints.Any(e => e.tag == tag))) + if (_coreConfig.outbounds.Any(o => o.tag.StartsWith(tag)) + || (_coreConfig.endpoints != null && _coreConfig.endpoints.Any(e => e.tag.StartsWith(tag)))) { return tag; } - if (node.ConfigType.IsGroupType()) - { - var ret = await GenGroupOutbound(node, singboxConfig, tag); - if (ret == 0) - { - return tag; - } - return Global.ProxyTag; - } + var proxyOutbounds = new CoreConfigSingboxService(context with { Node = node, }).BuildAllProxyOutbounds(tag); + FillRangeProxy(proxyOutbounds, _coreConfig, false); - var server = await GenServer(node); - if (server is null) - { - return Global.ProxyTag; - } - - server.tag = tag; - if (server is Endpoints4Sbox endpoint) - { - singboxConfig.endpoints ??= new(); - singboxConfig.endpoints.Add(endpoint); - } - else if (server is Outbound4Sbox outbound) - { - singboxConfig.outbounds.Add(outbound); - } - - return server.tag; + return tag; } } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRulesetService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRulesetService.cs index 7d26ca2ff6..bb4c4b3a7a 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRulesetService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRulesetService.cs @@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task ConvertGeo2Ruleset(SingboxConfig singboxConfig) + private void ConvertGeo2Ruleset() { static void AddRuleSets(List ruleSets, List? rule_set) { @@ -16,14 +16,14 @@ public partial class CoreConfigSingboxService var ruleSets = new List(); //convert route geosite & geoip to ruleset - foreach (var rule in singboxConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) + foreach (var rule in _coreConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) { rule.rule_set ??= new List(); rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList()); rule.geosite = null; AddRuleSets(ruleSets, rule.rule_set); } - foreach (var rule in singboxConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) + foreach (var rule in _coreConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) { rule.rule_set ??= new List(); rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList()); @@ -32,24 +32,24 @@ public partial class CoreConfigSingboxService } //convert dns geosite & geoip to ruleset - foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) + foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) { rule.rule_set ??= new List(); rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList()); rule.geosite = null; } - foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) + foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) { rule.rule_set ??= new List(); rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList()); rule.geoip = null; } - foreach (var dnsRule in singboxConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? []) + foreach (var dnsRule in _coreConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? []) { AddRuleSets(ruleSets, dnsRule.rule_set); } //rules in rules - foreach (var item in singboxConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? []) + foreach (var item in _coreConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? []) { foreach (var item2 in item ?? []) { @@ -60,7 +60,7 @@ public partial class CoreConfigSingboxService //load custom ruleset file List customRulesets = []; - var routing = await ConfigHandler.GetDefaultRouting(_config); + var routing = context.RoutingItem; if (routing.CustomRulesetPath4Singbox.IsNotEmpty()) { var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox); @@ -78,7 +78,7 @@ public partial class CoreConfigSingboxService var localSrss = Utils.GetBinPath("srss"); //Add ruleset srs - singboxConfig.route.rule_set = []; + _coreConfig.route.rule_set = []; foreach (var item in new HashSet(ruleSets)) { if (item.IsNullOrEmpty()) @@ -113,9 +113,7 @@ public partial class CoreConfigSingboxService }; } } - singboxConfig.route.rule_set.Add(customRuleset); + _coreConfig.route.rule_set.Add(customRuleset); } - - return 0; } } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxStatisticService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxStatisticService.cs index c3acd810b2..e2beff347d 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxStatisticService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxStatisticService.cs @@ -2,12 +2,12 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task GenExperimental(SingboxConfig singboxConfig) + private void GenExperimental() { //if (_config.guiItem.enableStatistics) { - singboxConfig.experimental ??= new Experimental4Sbox(); - singboxConfig.experimental.clash_api = new Clash_Api4Sbox() + _coreConfig.experimental ??= new Experimental4Sbox(); + _coreConfig.experimental.clash_api = new Clash_Api4Sbox() { external_controller = $"{Global.Loopback}:{AppManager.Instance.StatePort2}", }; @@ -15,15 +15,13 @@ public partial class CoreConfigSingboxService if (_config.CoreBasicItem.EnableCacheFile4Sbox) { - singboxConfig.experimental ??= new Experimental4Sbox(); - singboxConfig.experimental.cache_file = new CacheFile4Sbox() + _coreConfig.experimental ??= new Experimental4Sbox(); + _coreConfig.experimental.cache_file = new CacheFile4Sbox() { enabled = true, path = Utils.GetBinPath("cache.db"), - store_fakeip = _config.SimpleDNSItem.FakeIP == true + store_fakeip = context.SimpleDnsItem.FakeIP == true }; } - - return await Task.FromResult(0); } } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs index d753fb3c5e..3ad7159204 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs @@ -1,44 +1,35 @@ namespace ServiceLib.Services.CoreConfig; -public partial class CoreConfigV2rayService(Config config) +public partial class CoreConfigV2rayService(CoreConfigContext context) { - private readonly Config _config = config; private static readonly string _tag = "CoreConfigV2rayService"; + private readonly Config _config = context.AppConfig; + private readonly ProfileItem _node = context.Node; + + private V2rayConfig _coreConfig = new(); #region public gen function - public async Task GenerateClientConfigContent(ProfileItem node) + public RetResult GenerateClientConfigContent() { var ret = new RetResult(); try { - if (node == null - || !node.IsValid()) + if (_node == null + || !_node.IsValid()) { ret.Msg = ResUI.CheckServerSettings; return ret; } - if (node.GetNetwork() is nameof(ETransport.quic)) + if (_node.GetNetwork() is nameof(ETransport.quic)) { - ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; return ret; } ret.Msg = ResUI.InitialConfiguration; - if (node.ConfigType.IsGroupType()) - { - switch (node.ConfigType) - { - case EConfigType.PolicyGroup: - return await GenerateClientMultipleLoadConfig(node); - - case EConfigType.ProxyChain: - return await GenerateClientChainConfig(node); - } - } - var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); if (result.IsNullOrEmpty()) { @@ -46,30 +37,28 @@ public partial class CoreConfigV2rayService(Config config) return ret; } - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } - await GenLog(v2rayConfig); + GenLog(); - await GenInbounds(v2rayConfig); + GenInbounds(); - await GenOutbound(node, v2rayConfig.outbounds.First()); + GenOutbounds(); - await GenMoreOutbounds(node, v2rayConfig); + GenRouting(); - await GenRouting(v2rayConfig); + GenDns(); - await GenDns(node, v2rayConfig); - - await GenStatistic(v2rayConfig); + GenStatistic(); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; - ret.Data = await ApplyFullConfigTemplate(v2rayConfig); + ret.Data = ApplyFullConfigTemplate(); return ret; } catch (Exception ex) @@ -80,18 +69,11 @@ public partial class CoreConfigV2rayService(Config config) } } - public async Task GenerateClientMultipleLoadConfig(ProfileItem parentNode) + public RetResult GenerateClientSpeedtestConfig(List selecteds) { var ret = new RetResult(); - try { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - ret.Msg = ResUI.InitialConfiguration; var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); @@ -102,172 +84,8 @@ public partial class CoreConfigV2rayService(Config config) return ret; } - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - v2rayConfig.outbounds.RemoveAt(0); - - await GenLog(v2rayConfig); - await GenInbounds(v2rayConfig); - - var groupRet = await GenGroupOutbound(parentNode, v2rayConfig); - if (groupRet != 0) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenRouting(v2rayConfig); - await GenDns(null, v2rayConfig); - await GenStatistic(v2rayConfig); - - var defaultBalancerTag = $"{Global.ProxyTag}{Global.BalancerTagSuffix}"; - - //add rule - var rules = v2rayConfig.routing.rules; - if (rules?.Count > 0 && ((v2rayConfig.routing.balancers?.Count ?? 0) > 0)) - { - var balancerTagSet = v2rayConfig.routing.balancers - .Select(b => b.tag) - .ToHashSet(); - - foreach (var rule in rules) - { - if (rule.outboundTag == null) - { - continue; - } - - if (balancerTagSet.Contains(rule.outboundTag)) - { - rule.balancerTag = rule.outboundTag; - rule.outboundTag = null; - continue; - } - - var outboundWithSuffix = rule.outboundTag + Global.BalancerTagSuffix; - if (balancerTagSet.Contains(outboundWithSuffix)) - { - rule.balancerTag = outboundWithSuffix; - rule.outboundTag = null; - } - } - } - if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch) - { - v2rayConfig.routing.rules.Add(new() - { - ip = ["0.0.0.0/0", "::/0"], - balancerTag = defaultBalancerTag, - type = "field" - }); - } - else - { - v2rayConfig.routing.rules.Add(new() - { - network = "tcp,udp", - balancerTag = defaultBalancerTag, - type = "field" - }); - } - - ret.Success = true; - - ret.Data = await ApplyFullConfigTemplate(v2rayConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientChainConfig(ProfileItem parentNode) - { - var ret = new RetResult(); - - try - { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - v2rayConfig.outbounds.RemoveAt(0); - - await GenLog(v2rayConfig); - await GenInbounds(v2rayConfig); - - var groupRet = await GenGroupOutbound(parentNode, v2rayConfig); - if (groupRet != 0) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenRouting(v2rayConfig); - await GenDns(null, v2rayConfig); - await GenStatistic(v2rayConfig); - - ret.Success = true; - - ret.Data = await ApplyFullConfigTemplate(v2rayConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientSpeedtestConfig(List selecteds) - { - var ret = new RetResult(); - try - { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; @@ -285,10 +103,10 @@ public partial class CoreConfigV2rayService(Config config) Logging.SaveLog(_tag, ex); } - await GenLog(v2rayConfig); - v2rayConfig.inbounds.Clear(); - v2rayConfig.outbounds.Clear(); - v2rayConfig.routing.rules.Clear(); + GenLog(); + _coreConfig.inbounds.Clear(); + _coreConfig.outbounds.Clear(); + _coreConfig.routing.rules.Clear(); var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest); @@ -302,7 +120,7 @@ public partial class CoreConfigV2rayService(Config config) { continue; } - var item = await AppManager.Instance.GetProfileItem(it.IndexId); + var item = context.AllProxiesMap.GetValueOrDefault(it.IndexId); if (item is null || item.IsComplex() || !item.IsValid()) { continue; @@ -342,27 +160,40 @@ public partial class CoreConfigV2rayService(Config config) protocol = EInboundProtocol.mixed.ToString(), }; inbound.tag = inbound.protocol + inbound.port.ToString(); - v2rayConfig.inbounds.Add(inbound); + _coreConfig.inbounds.Add(inbound); + var tag = Global.ProxyTag + inbound.port.ToString(); + var isBalancer = false; //outbound - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(item, outbound); - outbound.tag = Global.ProxyTag + inbound.port.ToString(); - v2rayConfig.outbounds.Add(outbound); + var proxyOutbounds = + new CoreConfigV2rayService(context with { Node = item }).BuildAllProxyOutbounds(tag); + _coreConfig.outbounds.AddRange(proxyOutbounds); + if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1) + { + isBalancer = true; + var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing; + GenObservatory(multipleLoad, tag); + GenBalancer(multipleLoad, tag); + } //rule RulesItem4Ray rule = new() { inboundTag = new List { inbound.tag }, - outboundTag = outbound.tag, + outboundTag = tag, type = "field" }; - v2rayConfig.routing.rules.Add(rule); + if (isBalancer) + { + rule.balancerTag = tag; + rule.outboundTag = null; + } + _coreConfig.routing.rules.Add(rule); } //ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary()); ret.Success = true; - ret.Data = JsonUtils.Serialize(v2rayConfig); + ret.Data = JsonUtils.Serialize(_coreConfig); return ret; } catch (Exception ex) @@ -373,21 +204,21 @@ public partial class CoreConfigV2rayService(Config config) } } - public async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) + public RetResult GenerateClientSpeedtestConfig(int port) { var ret = new RetResult(); try { - if (node == null - || !node.IsValid()) + if (_node == null + || !_node.IsValid()) { ret.Msg = ResUI.CheckServerSettings; return ret; } - if (node.GetNetwork() is nameof(ETransport.quic)) + if (_node.GetNetwork() is nameof(ETransport.quic)) { - ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; return ret; } @@ -398,20 +229,19 @@ public partial class CoreConfigV2rayService(Config config) return ret; } - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } - await GenLog(v2rayConfig); - await GenOutbound(node, v2rayConfig.outbounds.First()); - await GenMoreOutbounds(node, v2rayConfig); + GenLog(); + GenOutbounds(); - v2rayConfig.routing.rules.Clear(); - v2rayConfig.inbounds.Clear(); - v2rayConfig.inbounds.Add(new() + _coreConfig.routing.rules.Clear(); + _coreConfig.inbounds.Clear(); + _coreConfig.inbounds.Add(new() { tag = $"{EInboundProtocol.socks}{port}", listen = Global.Loopback, @@ -421,7 +251,7 @@ public partial class CoreConfigV2rayService(Config config) ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; - ret.Data = JsonUtils.Serialize(v2rayConfig); + ret.Data = JsonUtils.Serialize(_coreConfig); return ret; } catch (Exception ex) diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs index 35a220c643..f294de460e 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs @@ -2,17 +2,17 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenObservatory(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag) + private void GenObservatory(EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag) { // Collect all existing subject selectors from both observatories var subjectSelectors = new List(); - subjectSelectors.AddRange(v2rayConfig.burstObservatory?.subjectSelector ?? []); - subjectSelectors.AddRange(v2rayConfig.observatory?.subjectSelector ?? []); + subjectSelectors.AddRange(_coreConfig.burstObservatory?.subjectSelector ?? []); + subjectSelectors.AddRange(_coreConfig.observatory?.subjectSelector ?? []); // Case 1: exact match already exists -> nothing to do if (subjectSelectors.Any(baseTagName.StartsWith)) { - return await Task.FromResult(0); + return; } // Case 2: prefix match exists -> reuse it and move to the first position @@ -21,28 +21,28 @@ public partial class CoreConfigV2rayService { baseTagName = matched; - if (v2rayConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true) + if (_coreConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true) { - v2rayConfig.burstObservatory.subjectSelector.Remove(baseTagName); - v2rayConfig.burstObservatory.subjectSelector.Insert(0, baseTagName); + _coreConfig.burstObservatory.subjectSelector.Remove(baseTagName); + _coreConfig.burstObservatory.subjectSelector.Insert(0, baseTagName); } - if (v2rayConfig.observatory?.subjectSelector?.Contains(baseTagName) == true) + if (_coreConfig.observatory?.subjectSelector?.Contains(baseTagName) == true) { - v2rayConfig.observatory.subjectSelector.Remove(baseTagName); - v2rayConfig.observatory.subjectSelector.Insert(0, baseTagName); + _coreConfig.observatory.subjectSelector.Remove(baseTagName); + _coreConfig.observatory.subjectSelector.Insert(0, baseTagName); } - return await Task.FromResult(0); + return; } // Case 3: need to create or insert based on multipleLoad type if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.Fallback) { - if (v2rayConfig.burstObservatory is null) + if (_coreConfig.burstObservatory is null) { // Create new burst observatory with default ping config - v2rayConfig.burstObservatory = new BurstObservatory4Ray + _coreConfig.burstObservatory = new BurstObservatory4Ray { subjectSelector = [baseTagName], pingConfig = new() @@ -56,16 +56,16 @@ public partial class CoreConfigV2rayService } else { - v2rayConfig.burstObservatory.subjectSelector ??= new(); - v2rayConfig.burstObservatory.subjectSelector.Add(baseTagName); + _coreConfig.burstObservatory.subjectSelector ??= new(); + _coreConfig.burstObservatory.subjectSelector.Add(baseTagName); } } else if (multipleLoad is EMultipleLoad.LeastPing) { - if (v2rayConfig.observatory is null) + if (_coreConfig.observatory is null) { // Create new observatory with default probe config - v2rayConfig.observatory = new Observatory4Ray + _coreConfig.observatory = new Observatory4Ray { subjectSelector = [baseTagName], probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl, @@ -75,15 +75,13 @@ public partial class CoreConfigV2rayService } else { - v2rayConfig.observatory.subjectSelector ??= new(); - v2rayConfig.observatory.subjectSelector.Add(baseTagName); + _coreConfig.observatory.subjectSelector ??= new(); + _coreConfig.observatory.subjectSelector.Add(baseTagName); } } - - return await Task.FromResult(0); } - private async Task GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string selector = Global.ProxyTag) + private void GenBalancer(EMultipleLoad multipleLoad, string selector = Global.ProxyTag) { var strategyType = multipleLoad switch { @@ -107,8 +105,7 @@ public partial class CoreConfigV2rayService }, tag = balancerTag, }; - v2rayConfig.routing.balancers ??= new(); - v2rayConfig.routing.balancers.Add(balancer); - return await Task.FromResult(balancerTag); + _coreConfig.routing.balancers ??= new(); + _coreConfig.routing.balancers.Add(balancer); } } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs index 1f2583ffd4..544d1d2e40 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs @@ -2,35 +2,39 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task ApplyFullConfigTemplate(V2rayConfig v2rayConfig) + private string ApplyFullConfigTemplate() { - var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray); + var fullConfigTemplate = context.FullConfigTemplate; if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty()) { - return JsonUtils.Serialize(v2rayConfig); + return JsonUtils.Serialize(_coreConfig); } var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplate.Config); if (fullConfigTemplateNode == null) { - return JsonUtils.Serialize(v2rayConfig); + return JsonUtils.Serialize(_coreConfig); } // Handle balancer and rules modifications (for multiple load scenarios) - if (v2rayConfig.routing?.balancers?.Count > 0) + if (_coreConfig.routing?.balancers?.Count > 0) { - var balancer = v2rayConfig.routing.balancers.First(); + var balancer = + _coreConfig.routing.balancers.FirstOrDefault(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix, null); // Modify existing rules in custom config - var rulesNode = fullConfigTemplateNode["routing"]?["rules"]; - if (rulesNode != null) + if (balancer != null) { - foreach (var rule in rulesNode.AsArray()) + var rulesNode = fullConfigTemplateNode["routing"]?["rules"]; + if (rulesNode != null) { - if (rule["outboundTag"]?.GetValue() == Global.ProxyTag) + foreach (var rule in rulesNode.AsArray()) { - rule.AsObject().Remove("outboundTag"); - rule["balancerTag"] = balancer.tag; + if (rule["outboundTag"]?.GetValue() == Global.ProxyTag) + { + rule.AsObject().Remove("outboundTag"); + rule["balancerTag"] = balancer.tag; + } } } } @@ -44,7 +48,7 @@ public partial class CoreConfigV2rayService // Handle balancers - append instead of override if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode) { - if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)) is JsonArray newBalancers) + if (JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers)) is JsonArray newBalancers) { foreach (var balancerNode in newBalancers) { @@ -54,33 +58,33 @@ public partial class CoreConfigV2rayService } else { - fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)); + fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers)); } } - if (v2rayConfig.observatory != null) + if (_coreConfig.observatory != null) { if (fullConfigTemplateNode["observatory"] == null) { - fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.observatory)); + fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.observatory)); } else { - var subjectSelector = v2rayConfig.observatory.subjectSelector; + var subjectSelector = _coreConfig.observatory.subjectSelector; subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue()) ?? []); fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList())); } } - if (v2rayConfig.burstObservatory != null) + if (_coreConfig.burstObservatory != null) { if (fullConfigTemplateNode["burstObservatory"] == null) { - fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.burstObservatory)); + fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.burstObservatory)); } else { - var subjectSelector = v2rayConfig.burstObservatory.subjectSelector; + var subjectSelector = _coreConfig.burstObservatory.subjectSelector; subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue()) ?? []); fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList())); } @@ -88,7 +92,7 @@ public partial class CoreConfigV2rayService var customOutboundsNode = new JsonArray(); - foreach (var outbound in v2rayConfig.outbounds) + foreach (var outbound in _coreConfig.outbounds) { if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom") { @@ -97,8 +101,8 @@ public partial class CoreConfigV2rayService continue; } } - else if ((!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()) - && ((outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true) == true)) + else if (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty() + && (outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true)) { var outboundAddress = outbound.settings?.servers?.FirstOrDefault()?.address ?? outbound.settings?.vnext?.FirstOrDefault()?.address @@ -123,6 +127,6 @@ public partial class CoreConfigV2rayService fullConfigTemplateNode["outbounds"] = customOutboundsNode; - return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode)); + return JsonUtils.Serialize(fullConfigTemplateNode); } } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs index 7de97240a9..10cd905b01 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs @@ -2,46 +2,45 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenDns(ProfileItem? node, V2rayConfig v2rayConfig) + private void GenDns() { try { - var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray); + var item = context.RawDnsItem; if (item is { Enabled: true }) { - var result = await GenDnsCompatible(node, v2rayConfig); + GenDnsCustom(); - if (v2rayConfig.routing.domainStrategy != Global.IPIfNonMatch) + if (_coreConfig.routing.domainStrategy != Global.IPIfNonMatch) { - return result; + return; } // DNS routing - var dnsObj = JsonUtils.SerializeToNode(v2rayConfig.dns); + var dnsObj = JsonUtils.SerializeToNode(_coreConfig.dns); if (dnsObj == null) { - return result; + return; } dnsObj["tag"] = Global.DnsTag; - v2rayConfig.dns = JsonUtils.Deserialize(JsonUtils.Serialize(dnsObj)); - v2rayConfig.routing.rules.Add(new RulesItem4Ray + _coreConfig.dns = JsonUtils.Deserialize(JsonUtils.Serialize(dnsObj)); + _coreConfig.routing.rules.Add(new RulesItem4Ray { type = "field", inboundTag = new List { Global.DnsTag }, outboundTag = Global.ProxyTag, }); - - return result; + return; } - var simpleDnsItem = _config.SimpleDNSItem; - var dnsItem = v2rayConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray(); + var simpleDnsItem = context.SimpleDnsItem; + var dnsItem = _coreConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray(); var strategy4Freedom = simpleDnsItem?.Strategy4Freedom ?? Global.AsIs; //Outbound Freedom domainStrategy if (strategy4Freedom.IsNotEmpty() && strategy4Freedom != Global.AsIs) { - var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); + var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); if (outbound != null) { outbound.settings = new() @@ -59,23 +58,23 @@ public partial class CoreConfigV2rayService var xraySupportConfigTypeNames = Global.XraySupportConfigType .Select(x => x == EConfigType.Hysteria2 ? "hysteria" : Global.ProtocolTypes[x]) .ToHashSet(); - v2rayConfig.outbounds + _coreConfig.outbounds .Where(t => xraySupportConfigTypeNames.Contains(t.protocol)) .ToList() .ForEach(outbound => outbound.targetStrategy = strategy4Proxy); } - await GenDnsServers(node, dnsItem, simpleDnsItem); - await GenDnsHosts(dnsItem, simpleDnsItem); + FillDnsServers(dnsItem); + FillDnsHosts(dnsItem); dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null; dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null; - if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch) + if (_coreConfig.routing.domainStrategy == Global.IPIfNonMatch) { // DNS routing dnsItem.tag = Global.DnsTag; - v2rayConfig.routing.rules.Add(new RulesItem4Ray + _coreConfig.routing.rules.Add(new RulesItem4Ray { type = "field", inboundTag = new List { Global.DnsTag }, @@ -83,17 +82,17 @@ public partial class CoreConfigV2rayService }); } - v2rayConfig.dns = dnsItem; + _coreConfig.dns = dnsItem; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenDnsServers(ProfileItem? node, Dns4Ray dnsItem, SimpleDNSItem simpleDNSItem) + private void FillDnsServers(Dns4Ray dnsItem) { + var simpleDNSItem = context.SimpleDnsItem; static List ParseDnsAddresses(string? dnsInput, string defaultAddress) { var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';') @@ -197,9 +196,9 @@ public partial class CoreConfigV2rayService } } - var routing = await ConfigHandler.GetDefaultRouting(_config); + var routing = context.RoutingItem; List? rules = null; - rules = JsonUtils.Deserialize>(routing.RuleSet) ?? []; + rules = JsonUtils.Deserialize>(routing?.RuleSet) ?? []; foreach (var item in rules) { if (!item.Enabled || item.Domain is null || item.Domain.Count == 0) @@ -246,27 +245,9 @@ public partial class CoreConfigV2rayService } } - if (Utils.IsDomain(node?.Address)) + if (context.ProtectDomainList.Count > 0) { - directDomainList.Add(node.Address); - } - - if (node?.Subid is not null) - { - var subItem = await AppManager.Instance.GetSubItem(node.Subid); - if (subItem is not null) - { - foreach (var profile in new[] { subItem.PrevProfile, subItem.NextProfile }) - { - var profileNode = await AppManager.Instance.GetProfileItemViaRemarks(profile); - if (profileNode is not null - && Global.XraySupportConfigType.Contains(profileNode.ConfigType) - && Utils.IsDomain(profileNode.Address)) - { - directDomainList.Add(profileNode.Address); - } - } - } + directDomainList.AddRange(context.ProtectDomainList); } dnsItem.servers ??= []; @@ -300,15 +281,14 @@ public partial class CoreConfigV2rayService var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress; dnsItem.servers.AddRange(defaultDnsServers); - - return 0; } - private async Task GenDnsHosts(Dns4Ray dnsItem, SimpleDNSItem simpleDNSItem) + private void FillDnsHosts(Dns4Ray dnsItem) { + var simpleDNSItem = context.SimpleDnsItem; if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty()) { - return await Task.FromResult(0); + return; } dnsItem.hosts ??= new Dictionary(); if (simpleDNSItem.AddCommonHosts == true) @@ -337,14 +317,13 @@ public partial class CoreConfigV2rayService { dnsItem.hosts[kvp.Key] = kvp.Value; } - return await Task.FromResult(0); } - private async Task GenDnsCompatible(ProfileItem? node, V2rayConfig v2rayConfig) + private void GenDnsCustom() { try { - var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray); + var item = context.RawDnsItem; var normalDNS = item?.NormalDNS; var domainStrategy4Freedom = item?.DomainStrategy4Freedom; if (normalDNS.IsNullOrEmpty()) @@ -355,7 +334,7 @@ public partial class CoreConfigV2rayService //Outbound Freedom domainStrategy if (domainStrategy4Freedom.IsNotEmpty()) { - var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); + var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); if (outbound != null) { outbound.settings = new(); @@ -410,63 +389,37 @@ public partial class CoreConfigV2rayService } } - await GenDnsDomainsCompatible(node, obj, item); + FillDnsDomainsCustom(obj); - v2rayConfig.dns = JsonUtils.Deserialize(JsonUtils.Serialize(obj)); + _coreConfig.dns = JsonUtils.Deserialize(JsonUtils.Serialize(obj)); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenDnsDomainsCompatible(ProfileItem? node, JsonNode dns, DNSItem? dnsItem) + private void FillDnsDomainsCustom(JsonNode dns) { - if (node == null) - { - return 0; - } var servers = dns["servers"]; - if (servers != null) + if (servers == null) { - var domainList = new List(); - if (Utils.IsDomain(node.Address)) - { - domainList.Add(node.Address); - } - var subItem = await AppManager.Instance.GetSubItem(node.Subid); - if (subItem is not null) - { - // Previous proxy - var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - if (prevNode is not null - && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType) - && Utils.IsDomain(prevNode.Address)) - { - domainList.Add(prevNode.Address); - } - - // Next proxy - var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode is not null - && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType) - && Utils.IsDomain(nextNode.Address)) - { - domainList.Add(nextNode.Address); - } - } - if (domainList.Count > 0) - { - var dnsServer = new DnsServer4Ray() - { - address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress, - skipFallback = true, - domains = domainList - }; - servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer)); - } + return; } - return await Task.FromResult(0); + + var domainList = context.ProtectDomainList; + if (domainList.Count <= 0) + { + return; + } + + var dnsItem = context.RawDnsItem; + var dnsServer = new DnsServer4Ray() + { + address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress, + skipFallback = true, + domains = domainList.ToList(), + }; + servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer)); } } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs index 2cbdfe88d7..7ae12c7caf 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs @@ -2,35 +2,35 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenInbounds(V2rayConfig v2rayConfig) + private void GenInbounds() { try { var listen = "0.0.0.0"; - v2rayConfig.inbounds = []; + _coreConfig.inbounds = []; - var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.socks, true); - v2rayConfig.inbounds.Add(inbound); + var inbound = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks, true); + _coreConfig.inbounds.Add(inbound); if (_config.Inbound.First().SecondLocalPortEnabled) { - var inbound2 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks2, true); - v2rayConfig.inbounds.Add(inbound2); + var inbound2 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks2, true); + _coreConfig.inbounds.Add(inbound2); } if (_config.Inbound.First().AllowLANConn) { if (_config.Inbound.First().NewPort4LAN) { - var inbound3 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks3, true); + var inbound3 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks3, true); inbound3.listen = listen; - v2rayConfig.inbounds.Add(inbound3); + _coreConfig.inbounds.Add(inbound3); //auth if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) { inbound3.settings.auth = "password"; - inbound3.settings.accounts = new List { new AccountsItem4Ray() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } }; + inbound3.settings.accounts = new List { new() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } }; } } else @@ -43,10 +43,9 @@ public partial class CoreConfigV2rayService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks) + private Inbounds4Ray BuildInbound(InItem inItem, EInboundProtocol protocol, bool bSocks) { var result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound); if (result.IsNullOrEmpty()) diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayLogService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayLogService.cs index 5b9344fb6c..86f4b2c2f6 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayLogService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayLogService.cs @@ -2,28 +2,27 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenLog(V2rayConfig v2rayConfig) + private void GenLog() { try { if (_config.CoreBasicItem.LogEnabled) { var dtNow = DateTime.Now; - v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel; - v2rayConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt"); - v2rayConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt"); + _coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel; + _coreConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt"); + _coreConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt"); } else { - v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel; - v2rayConfig.log.access = null; - v2rayConfig.log.error = null; + _coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel; + _coreConfig.log.access = null; + _coreConfig.log.error = null; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs index 7f9c26cabf..a6bb21ecfc 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs @@ -2,13 +2,94 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenOutbound(ProfileItem node, Outbounds4Ray outbound) + private void GenOutbounds() + { + var proxyOutboundList = BuildAllProxyOutbounds(); + _coreConfig.outbounds.InsertRange(0, proxyOutboundList); + if (proxyOutboundList.Count(n => n.tag.StartsWith(Global.ProxyTag)) > 1) + { + var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing; + GenObservatory(multipleLoad); + GenBalancer(multipleLoad); + } + } + + private List BuildAllProxyOutbounds(string baseTagName = Global.ProxyTag) + { + var proxyOutboundList = new List(); + if (_node.ConfigType.IsGroupType()) + { + proxyOutboundList.AddRange(BuildGroupProxyOutbounds(baseTagName)); + } + else + { + proxyOutboundList.Add(BuildProxyOutbound(baseTagName)); + } + + if (_config.CoreBasicItem.EnableFragment) + { + var fragmentOutbound = new Outbounds4Ray + { + protocol = "freedom", + tag = $"frag-{Global.ProxyTag}", + settings = new() + { + fragment = new() + { + packets = _config.Fragment4RayItem?.Packets, + length = _config.Fragment4RayItem?.Length, + interval = _config.Fragment4RayItem?.Interval + } + } + }; + var actOutboundWithTlsList = + proxyOutboundList.Where(n => n.streamSettings?.security.IsNullOrEmpty() == false + && (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true)); + foreach (var outbound in actOutboundWithTlsList) + { + var fragmentOutboundClone = JsonUtils.DeepCopy(fragmentOutbound); + fragmentOutboundClone.tag = $"frag-{outbound.tag}"; + outbound.streamSettings.sockopt = new() + { + dialerProxy = fragmentOutboundClone.tag + }; + proxyOutboundList.Add(fragmentOutboundClone); + } + } + return proxyOutboundList; + } + + private List BuildGroupProxyOutbounds(string baseTagName = Global.ProxyTag) + { + var proxyOutboundList = new List(); + switch (_node.ConfigType) + { + case EConfigType.PolicyGroup: + proxyOutboundList.AddRange(BuildOutboundsList(baseTagName)); + break; + case EConfigType.ProxyChain: + proxyOutboundList.AddRange(BuildChainOutboundsList(baseTagName)); + break; + } + return proxyOutboundList; + } + + private Outbounds4Ray BuildProxyOutbound(string baseTagName = Global.ProxyTag) + { + var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); + var outbound = JsonUtils.Deserialize(txtOutbound); + FillOutbound(outbound); + outbound.tag = baseTagName; + return outbound; + } + + private void FillOutbound(Outbounds4Ray outbound) { try { - var protocolExtra = node.GetProtocolExtra(); - var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; - switch (node.ConfigType) + var protocolExtra = _node.GetProtocolExtra(); + var muxEnabled = _node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; + switch (_node.ConfigType) { case EConfigType.VMess: { @@ -22,8 +103,8 @@ public partial class CoreConfigV2rayService { vnextItem = outbound.settings.vnext.First(); } - vnextItem.address = node.Address; - vnextItem.port = node.Port; + vnextItem.address = _node.Address; + vnextItem.port = _node.Port; UsersItem4Ray usersItem; if (vnextItem.users.Count <= 0) @@ -36,7 +117,7 @@ public partial class CoreConfigV2rayService usersItem = vnextItem.users.First(); } - usersItem.id = node.Password; + usersItem.id = _node.Password; usersItem.alterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0; usersItem.email = Global.UserEMail; if (Global.VmessSecurities.Contains(protocolExtra.VmessSecurity)) @@ -48,7 +129,7 @@ public partial class CoreConfigV2rayService usersItem.security = Global.DefaultSecurity; } - await GenOutboundMux(node, outbound, muxEnabled, muxEnabled); + FillOutboundMux(outbound, muxEnabled, muxEnabled); outbound.settings.servers = null; break; @@ -65,16 +146,16 @@ public partial class CoreConfigV2rayService { serversItem = outbound.settings.servers.First(); } - serversItem.address = node.Address; - serversItem.port = node.Port; - serversItem.password = node.Password; - serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod) + serversItem.address = _node.Address; + serversItem.port = _node.Port; + serversItem.password = _node.Password; + serversItem.method = AppManager.Instance.GetShadowsocksSecurities(_node).Contains(protocolExtra.SsMethod) ? protocolExtra.SsMethod : "none"; serversItem.ota = false; serversItem.level = 1; - await GenOutboundMux(node, outbound); + FillOutboundMux(outbound); outbound.settings.vnext = null; break; @@ -92,25 +173,25 @@ public partial class CoreConfigV2rayService { serversItem = outbound.settings.servers.First(); } - serversItem.address = node.Address; - serversItem.port = node.Port; + serversItem.address = _node.Address; + serversItem.port = _node.Port; serversItem.method = null; serversItem.password = null; - if (node.Username.IsNotEmpty() - && node.Password.IsNotEmpty()) + if (_node.Username.IsNotEmpty() + && _node.Password.IsNotEmpty()) { SocksUsersItem4Ray socksUsersItem = new() { - user = node.Username ?? "", - pass = node.Password, + user = _node.Username ?? "", + pass = _node.Password, level = 1 }; serversItem.users = new List() { socksUsersItem }; } - await GenOutboundMux(node, outbound); + FillOutboundMux(outbound); outbound.settings.vnext = null; break; @@ -127,8 +208,8 @@ public partial class CoreConfigV2rayService { vnextItem = outbound.settings.vnext.First(); } - vnextItem.address = node.Address; - vnextItem.port = node.Port; + vnextItem.address = _node.Address; + vnextItem.port = _node.Port; UsersItem4Ray usersItem; if (vnextItem.users.Count <= 0) @@ -140,7 +221,7 @@ public partial class CoreConfigV2rayService { usersItem = vnextItem.users.First(); } - usersItem.id = node.Password; + usersItem.id = _node.Password; usersItem.email = Global.UserEMail; usersItem.encryption = protocolExtra.VlessEncryption; @@ -150,7 +231,7 @@ public partial class CoreConfigV2rayService } else { - await GenOutboundMux(node, outbound, false, muxEnabled); + FillOutboundMux(outbound, false, muxEnabled); } outbound.settings.servers = null; break; @@ -167,14 +248,14 @@ public partial class CoreConfigV2rayService { serversItem = outbound.settings.servers.First(); } - serversItem.address = node.Address; - serversItem.port = node.Port; - serversItem.password = node.Password; + serversItem.address = _node.Address; + serversItem.port = _node.Port; + serversItem.password = _node.Password; serversItem.ota = false; serversItem.level = 1; - await GenOutboundMux(node, outbound); + FillOutboundMux(outbound); outbound.settings.vnext = null; break; @@ -184,8 +265,8 @@ public partial class CoreConfigV2rayService outbound.settings = new() { version = 2, - address = node.Address, - port = node.Port, + address = _node.Address, + port = _node.Port, }; outbound.settings.vnext = null; outbound.settings.servers = null; @@ -193,7 +274,7 @@ public partial class CoreConfigV2rayService } case EConfigType.WireGuard: { - var address = node.Address; + var address = _node.Address; if (Utils.IsIpv6(address)) { address = $"[{address}]"; @@ -201,12 +282,12 @@ public partial class CoreConfigV2rayService var peer = new WireguardPeer4Ray { publicKey = protocolExtra.WgPublicKey ?? "", - endpoint = address + ":" + node.Port.ToString() + endpoint = address + ":" + _node.Port.ToString() }; var setting = new Outboundsettings4Ray { address = Utils.String2List(protocolExtra.WgInterfaceAddress), - secretKey = node.Password, + secretKey = _node.Password, reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(), mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(), peers = [peer] @@ -218,21 +299,20 @@ public partial class CoreConfigV2rayService } } - outbound.protocol = Global.ProtocolTypes[node.ConfigType]; - if (node.ConfigType == EConfigType.Hysteria2) + outbound.protocol = Global.ProtocolTypes[_node.ConfigType]; + if (_node.ConfigType == EConfigType.Hysteria2) { outbound.protocol = "hysteria"; } - await GenBoundStreamSettings(node, outbound); + FillBoundStreamSettings(outbound); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenOutboundMux(ProfileItem node, Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false) + private void FillOutboundMux(Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false) { try { @@ -255,48 +335,40 @@ public partial class CoreConfigV2rayService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenBoundStreamSettings(ProfileItem node, Outbounds4Ray outbound) + private void FillBoundStreamSettings(Outbounds4Ray outbound) { try { var streamSettings = outbound.streamSettings; - var network = node.GetNetwork(); - if (node.ConfigType == EConfigType.Hysteria2) + var network = _node.GetNetwork(); + if (_node.ConfigType == EConfigType.Hysteria2) { network = "hysteria"; } streamSettings.network = network; - var host = node.RequestHost.TrimEx(); - var path = node.Path.TrimEx(); - var sni = node.Sni.TrimEx(); + var host = _node.RequestHost.TrimEx(); + var path = _node.Path.TrimEx(); + var sni = _node.Sni.TrimEx(); var useragent = ""; if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty()) { - try - { - useragent = Global.UserAgentTexts[_config.CoreBasicItem.DefUserAgent]; - } - catch (KeyNotFoundException) - { - useragent = _config.CoreBasicItem.DefUserAgent; - } + useragent = Global.UserAgentTexts.GetValueOrDefault(_config.CoreBasicItem.DefUserAgent, _config.CoreBasicItem.DefUserAgent); } //if tls - if (node.StreamSecurity == Global.StreamSecurity) + if (_node.StreamSecurity == Global.StreamSecurity) { - streamSettings.security = node.StreamSecurity; + streamSettings.security = _node.StreamSecurity; TlsSettings4Ray tlsSettings = new() { - allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure), - alpn = node.GetAlpn(), - fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint, - echConfigList = node.EchConfigList.NullIfEmpty(), - echForceQuery = node.EchForceQuery.NullIfEmpty() + allowInsecure = Utils.ToBool(_node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : _node.AllowInsecure), + alpn = _node.GetAlpn(), + fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint, + echConfigList = _node.EchConfigList.NullIfEmpty(), + echForceQuery = _node.EchForceQuery.NullIfEmpty() }; if (sni.IsNotEmpty()) { @@ -306,7 +378,7 @@ public partial class CoreConfigV2rayService { tlsSettings.serverName = Utils.String2List(host)?.First(); } - var certs = CertPemManager.ParsePemChain(node.Cert); + var certs = CertPemManager.ParsePemChain(_node.Cert); if (certs.Count > 0) { var certsettings = new List(); @@ -323,27 +395,27 @@ public partial class CoreConfigV2rayService tlsSettings.disableSystemRoot = true; tlsSettings.allowInsecure = false; } - else if (!node.CertSha.IsNullOrEmpty()) + else if (!_node.CertSha.IsNullOrEmpty()) { - tlsSettings.pinnedPeerCertSha256 = node.CertSha; + tlsSettings.pinnedPeerCertSha256 = _node.CertSha; tlsSettings.allowInsecure = false; } streamSettings.tlsSettings = tlsSettings; } //if Reality - if (node.StreamSecurity == Global.StreamSecurityReality) + if (_node.StreamSecurity == Global.StreamSecurityReality) { - streamSettings.security = node.StreamSecurity; + streamSettings.security = _node.StreamSecurity; TlsSettings4Ray realitySettings = new() { - fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint, + fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint, serverName = sni, - publicKey = node.PublicKey, - shortId = node.ShortId, - spiderX = node.SpiderX, - mldsa65Verify = node.Mldsa65Verify, + publicKey = _node.PublicKey, + shortId = _node.ShortId, + spiderX = _node.SpiderX, + mldsa65Verify = _node.Mldsa65Verify, show = false, }; @@ -367,14 +439,14 @@ public partial class CoreConfigV2rayService kcpSettings.readBufferSize = _config.KcpItem.ReadBufferSize; kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize; streamSettings.finalmask ??= new(); - if (Global.KcpHeaderMaskMap.TryGetValue(node.HeaderType, out var header)) + if (Global.KcpHeaderMaskMap.TryGetValue(_node.HeaderType, out var header)) { streamSettings.finalmask.udp = [ new Mask4Ray { type = header, - settings = node.HeaderType == "dns" && !host.IsNullOrEmpty() ? new MaskSettings4Ray { domain = host } : null + settings = _node.HeaderType == "dns" && !host.IsNullOrEmpty() ? new MaskSettings4Ray { domain = host } : null } ]; } @@ -444,17 +516,17 @@ public partial class CoreConfigV2rayService { xhttpSettings.host = host; } - if (node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(node.HeaderType)) + if (_node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(_node.HeaderType)) { - xhttpSettings.mode = node.HeaderType; + xhttpSettings.mode = _node.HeaderType; } - if (node.Extra.IsNotEmpty()) + if (_node.Extra.IsNotEmpty()) { - xhttpSettings.extra = JsonUtils.ParseJson(node.Extra); + xhttpSettings.extra = JsonUtils.ParseJson(_node.Extra); } streamSettings.xhttpSettings = xhttpSettings; - await GenOutboundMux(node, outbound); + FillOutboundMux(outbound); break; //h2 @@ -478,11 +550,11 @@ public partial class CoreConfigV2rayService key = path, header = new Header4Ray { - type = node.HeaderType + type = _node.HeaderType } }; streamSettings.quicSettings = quicsettings; - if (node.StreamSecurity == Global.StreamSecurity) + if (_node.StreamSecurity == Global.StreamSecurity) { if (sni.IsNotEmpty()) { @@ -490,7 +562,7 @@ public partial class CoreConfigV2rayService } else { - streamSettings.tlsSettings.serverName = node.Address; + streamSettings.tlsSettings.serverName = _node.Address; } } break; @@ -500,7 +572,7 @@ public partial class CoreConfigV2rayService { authority = host.NullIfEmpty(), serviceName = path, - multiMode = node.HeaderType == Global.GrpcMultiMode, + multiMode = _node.HeaderType == Global.GrpcMultiMode, idle_timeout = _config.GrpcItem.IdleTimeout, health_check_timeout = _config.GrpcItem.HealthCheckTimeout, permit_without_stream = _config.GrpcItem.PermitWithoutStream, @@ -510,7 +582,7 @@ public partial class CoreConfigV2rayService break; case "hysteria": - var protocolExtra = node.GetProtocolExtra(); + var protocolExtra = _node.GetProtocolExtra(); var ports = protocolExtra?.Ports; int? upMbps = protocolExtra?.UpMbps is { } su and >= 0 ? su @@ -536,7 +608,7 @@ public partial class CoreConfigV2rayService streamSettings.hysteriaSettings = new() { version = 2, - auth = node.Password, + auth = _node.Password, up = upMbps > 0 ? $"{upMbps}mbps" : null, down = downMbps > 0 ? $"{downMbps}mbps" : null, udphop = udpHop, @@ -557,13 +629,13 @@ public partial class CoreConfigV2rayService default: //tcp - if (node.HeaderType == Global.TcpHeaderHttp) + if (_node.HeaderType == Global.TcpHeaderHttp) { TcpSettings4Ray tcpSettings = new() { header = new Header4Ray { - type = node.HeaderType + type = _node.HeaderType } }; @@ -587,415 +659,142 @@ public partial class CoreConfigV2rayService } break; } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return 0; - } - private async Task GenGroupOutbound(ProfileItem node, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false) - { - try - { - if (!node.ConfigType.IsGroupType()) + if (!_node.Finalmask.IsNullOrEmpty()) { - return -1; - } - var hasCycle = await GroupProfileManager.HasCycle(node); - if (hasCycle) - { - return -1; - } - - var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node); - if (childProfiles.Count <= 0) - { - return -1; - } - switch (node.ConfigType) - { - case EConfigType.PolicyGroup: - if (ignoreOriginChain) - { - await GenOutboundsList(childProfiles, v2rayConfig, baseTagName); - } - else - { - await GenOutboundsListWithChain(childProfiles, v2rayConfig, baseTagName); - } - break; - - case EConfigType.ProxyChain: - await GenChainOutboundsList(childProfiles, v2rayConfig, baseTagName); - break; - - default: - break; - } - - //add balancers - if (node.ConfigType == EConfigType.PolicyGroup) - { - var multipleLoad = profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing; - await GenObservatory(v2rayConfig, multipleLoad, baseTagName); - await GenBalancer(v2rayConfig, multipleLoad, baseTagName); + streamSettings.finalmask = JsonUtils.Deserialize(_node.Finalmask); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenMoreOutbounds(ProfileItem node, V2rayConfig v2rayConfig) + private List BuildOutboundsList(string baseTagName = Global.ProxyTag) { - //fragment proxy - if (_config.CoreBasicItem.EnableFragment - && v2rayConfig.outbounds.First().streamSettings?.security.IsNullOrEmpty() == false) + var nodes = new List(); + foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? []) { - var fragmentOutbound = new Outbounds4Ray + if (context.AllProxiesMap.TryGetValue(nodeId, out var node)) { - protocol = "freedom", - tag = $"frag-{Global.ProxyTag}", - settings = new() - { - fragment = new() - { - packets = _config.Fragment4RayItem?.Packets, - length = _config.Fragment4RayItem?.Length, - interval = _config.Fragment4RayItem?.Interval - } - } - }; - - v2rayConfig.outbounds.Add(fragmentOutbound); - v2rayConfig.outbounds.First().streamSettings.sockopt = new() - { - dialerProxy = fragmentOutbound.tag - }; - return 0; - } - - if (node.Subid.IsNullOrEmpty()) - { - return 0; - } - try - { - var subItem = await AppManager.Instance.GetSubItem(node.Subid); - if (subItem is null) - { - return 0; - } - - //current proxy - var outbound = v2rayConfig.outbounds.First(); - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - - //Previous proxy - var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - string? prevOutboundTag = null; - if (prevNode is not null - && Global.XraySupportConfigType.Contains(prevNode.ConfigType)) - { - var prevOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(prevNode, prevOutbound); - prevOutboundTag = $"prev-{Global.ProxyTag}"; - prevOutbound.tag = prevOutboundTag; - v2rayConfig.outbounds.Add(prevOutbound); - } - var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag); - - if (nextOutbound is not null) - { - v2rayConfig.outbounds.Insert(0, nextOutbound); + nodes.Add(node); } } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return 0; - } - - private async Task GenOutboundsListWithChain(List nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag) - { - try - { - // Get template and initialize list - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - if (txtOutbound.IsNullOrEmpty()) - { - return 0; - } - - var resultOutbounds = new List(); - var prevOutbounds = new List(); // Separate list for prev outbounds and fragment - - // Cache for chain proxies to avoid duplicate generation - var nextProxyCache = new Dictionary(); - var prevProxyTags = new Dictionary(); // Map from profile name to tag - var prevIndex = 0; // Index for prev outbounds - - // Process nodes - var index = 0; - foreach (var node in nodes) - { - index++; - - if (node.ConfigType.IsGroupType()) - { - var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node); - if (childProfiles.Count <= 0) - { - continue; - } - var childBaseTagName = $"{baseTagName}-{index}"; - var ret = node.ConfigType switch - { - EConfigType.PolicyGroup => - await GenOutboundsListWithChain(childProfiles, v2rayConfig, childBaseTagName), - EConfigType.ProxyChain => - await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName), - _ => throw new NotImplementedException() - }; - continue; - } - - // Handle proxy chain - string? prevTag = null; - var currentOutbound = JsonUtils.Deserialize(txtOutbound); - var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null); - if (nextOutbound != null) - { - nextOutbound = JsonUtils.DeepCopy(nextOutbound); - } - - var subItem = await AppManager.Instance.GetSubItem(node.Subid); - - // current proxy - await GenOutbound(node, currentOutbound); - currentOutbound.tag = $"{baseTagName}-{index}"; - - if (!node.Subid.IsNullOrEmpty()) - { - if (prevProxyTags.TryGetValue(node.Subid, out var value)) - { - prevTag = value; // maybe null - } - else - { - var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - if (prevNode is not null - && Global.XraySupportConfigType.Contains(prevNode.ConfigType)) - { - var prevOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(prevNode, prevOutbound); - prevTag = $"prev-{baseTagName}-{++prevIndex}"; - prevOutbound.tag = prevTag; - prevOutbounds.Add(prevOutbound); - } - prevProxyTags[node.Subid] = prevTag; - } - - nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound); - if (!nextProxyCache.ContainsKey(node.Subid)) - { - nextProxyCache[node.Subid] = nextOutbound; - } - } - - if (nextOutbound is not null) - { - resultOutbounds.Add(nextOutbound); - } - resultOutbounds.Add(currentOutbound); - } - - // Merge results: first the main chain outbounds, then other outbounds, and finally utility outbounds - if (baseTagName == Global.ProxyTag) - { - resultOutbounds.AddRange(prevOutbounds); - resultOutbounds.AddRange(v2rayConfig.outbounds); - v2rayConfig.outbounds = resultOutbounds; - } - else - { - v2rayConfig.outbounds.AddRange(prevOutbounds); - v2rayConfig.outbounds.AddRange(resultOutbounds); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return 0; - } - - /// - /// Generates a chained outbound configuration for the given subItem and outbound. - /// The outbound's tag must be set before calling this method. - /// Returns the next proxy's outbound configuration, which may be null if no next proxy exists. - /// - /// The subscription item containing proxy chain information. - /// The current outbound configuration. Its tag must be set before calling this method. - /// The tag of the previous outbound in the chain, if any. - /// The outbound for the next proxy in the chain, if already created. If null, will be created inside. - /// - /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. - /// - private async Task GenChainOutbounds(SubItem subItem, Outbounds4Ray outbound, string? prevOutboundTag, Outbounds4Ray? nextOutbound = null) - { - try - { - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - - if (!prevOutboundTag.IsNullOrEmpty()) - { - outbound.streamSettings.sockopt = new() - { - dialerProxy = prevOutboundTag - }; - } - - // Next proxy - var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode is not null - && Global.XraySupportConfigType.Contains(nextNode.ConfigType)) - { - if (nextOutbound == null) - { - nextOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(nextNode, nextOutbound); - } - nextOutbound.tag = outbound.tag; - - outbound.tag = $"mid-{outbound.tag}"; - nextOutbound.streamSettings.sockopt = new() - { - dialerProxy = outbound.tag - }; - } - return nextOutbound; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return null; - } - - private async Task GenOutboundsList(List nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag) - { var resultOutbounds = new List(); for (var i = 0; i < nodes.Count; i++) { var node = nodes[i]; - if (node == null) - { - continue; - } + var currentTag = $"{baseTagName}-{i + 1}"; if (node.ConfigType.IsGroupType()) { - var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node); - if (childProfiles.Count <= 0) - { - continue; - } - var childBaseTagName = $"{baseTagName}-{i + 1}"; - var ret = node.ConfigType switch - { - EConfigType.PolicyGroup => - await GenOutboundsListWithChain(childProfiles, v2rayConfig, childBaseTagName), - EConfigType.ProxyChain => - await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName), - _ => throw new NotImplementedException() - }; + var childProfiles = new CoreConfigV2rayService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag); + resultOutbounds.AddRange(childProfiles); continue; } - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - if (txtOutbound.IsNullOrEmpty()) - { - break; - } - var outbound = JsonUtils.Deserialize(txtOutbound); - var result = await GenOutbound(node, outbound); - if (result != 0) - { - break; - } - outbound.tag = baseTagName + (i + 1).ToString(); + var outbound = new CoreConfigV2rayService(context with { Node = node, }).BuildProxyOutbound(); + outbound.tag = currentTag; resultOutbounds.Add(outbound); } - if (baseTagName == Global.ProxyTag) - { - resultOutbounds.AddRange(v2rayConfig.outbounds); - v2rayConfig.outbounds = resultOutbounds; - } - else - { - v2rayConfig.outbounds.AddRange(resultOutbounds); - } - return await Task.FromResult(0); + return resultOutbounds; } - private async Task GenChainOutboundsList(List nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag) + private List BuildChainOutboundsList(string baseTagName = Global.ProxyTag) { + var nodes = new List(); + foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? []) + { + if (context.AllProxiesMap.TryGetValue(nodeId, out var node)) + { + nodes.Add(node); + } + } // Based on actual network flow instead of data packets var nodesReverse = nodes.AsEnumerable().Reverse().ToList(); var resultOutbounds = new List(); for (var i = 0; i < nodesReverse.Count; i++) { var node = nodesReverse[i]; - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - if (txtOutbound.IsNullOrEmpty()) + var currentTag = i == 0 ? baseTagName : $"chain-{baseTagName}-{i}"; + var dialerProxyTag = i != nodesReverse.Count - 1 ? $"chain-{baseTagName}-{i + 1}" : null; + if (node.ConfigType.IsGroupType()) { - break; + var childProfiles = new CoreConfigV2rayService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag); + if (!dialerProxyTag.IsNullOrEmpty()) + { + var chainEndNodes = + childProfiles.Where(n => n?.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true); + foreach (var chainEndNode in chainEndNodes) + { + chainEndNode.streamSettings.sockopt = new() + { + dialerProxy = dialerProxyTag + }; + } + } + if (i != 0) + { + var chainStartNodes = childProfiles.Where(n => n.tag.StartsWith(currentTag)).ToList(); + if (chainStartNodes.Count == 1) + { + foreach (var existedChainEndNode in resultOutbounds.Where(n => n.streamSettings?.sockopt?.dialerProxy == currentTag)) + { + existedChainEndNode.streamSettings.sockopt = new() + { + dialerProxy = chainStartNodes.First().tag + }; + } + } + else if (chainStartNodes.Count > 1) + { + var existedChainNodes = JsonUtils.DeepCopy(resultOutbounds); + resultOutbounds.Clear(); + var j = 0; + foreach (var chainStartNode in chainStartNodes) + { + var existedChainNodesClone = JsonUtils.DeepCopy(existedChainNodes); + foreach (var existedChainNode in existedChainNodesClone) + { + var cloneTag = $"{existedChainNode.tag}-clone-{j + 1}"; + existedChainNode.tag = cloneTag; + } + for (var k = 0; k < existedChainNodesClone.Count; k++) + { + var existedChainNode = existedChainNodesClone[k]; + var previousDialerProxyTag = existedChainNode.streamSettings?.sockopt?.dialerProxy; + var nextTag = k + 1 < existedChainNodesClone.Count + ? existedChainNodesClone[k + 1].tag + : chainStartNode.tag; + existedChainNode.streamSettings.sockopt = new() + { + dialerProxy = (previousDialerProxyTag == currentTag) + ? chainStartNode.tag + : nextTag + }; + resultOutbounds.Add(existedChainNode); + } + j++; + } + } + } + resultOutbounds.AddRange(childProfiles); + continue; } - var outbound = JsonUtils.Deserialize(txtOutbound); - var result = await GenOutbound(node, outbound); + var outbound = new CoreConfigV2rayService(context with { Node = node, }).BuildProxyOutbound(); - if (result != 0) - { - break; - } + outbound.tag = currentTag; - if (i == 0) - { - outbound.tag = baseTagName; - } - else - { - // avoid v2ray observe - outbound.tag = "chain-" + baseTagName + i.ToString(); - } - - if (i != nodesReverse.Count - 1) + if (!dialerProxyTag.IsNullOrEmpty()) { outbound.streamSettings.sockopt = new() { - dialerProxy = "chain-" + baseTagName + (i + 1).ToString() + dialerProxy = dialerProxyTag }; } resultOutbounds.Add(outbound); } - if (baseTagName == Global.ProxyTag) - { - resultOutbounds.AddRange(v2rayConfig.outbounds); - v2rayConfig.outbounds = resultOutbounds; - } - else - { - v2rayConfig.outbounds.AddRange(resultOutbounds); - } - - return await Task.FromResult(0); + return resultOutbounds; } } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs index 8691abb152..1d3d1a37e5 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs @@ -2,20 +2,20 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenRouting(V2rayConfig v2rayConfig) + private void GenRouting() { try { - if (v2rayConfig.routing?.rules != null) + if (_coreConfig.routing?.rules != null) { - v2rayConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy; + _coreConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy; - var routing = await ConfigHandler.GetDefaultRouting(_config); + var routing = context.RoutingItem; if (routing != null) { if (routing.DomainStrategy.IsNotEmpty()) { - v2rayConfig.routing.domainStrategy = routing.DomainStrategy; + _coreConfig.routing.domainStrategy = routing.DomainStrategy; } var rules = JsonUtils.Deserialize>(routing.RuleSet); foreach (var item in rules) @@ -31,7 +31,18 @@ public partial class CoreConfigV2rayService } var item2 = JsonUtils.Deserialize(JsonUtils.Serialize(item)); - await GenRoutingUserRule(item2, v2rayConfig); + GenRoutingUserRule(item2); + } + } + var balancerTagList = _coreConfig.routing.balancers + ?.Select(p => p.tag) + .ToList() ?? []; + if (balancerTagList.Count > 0) + { + foreach (var rulesItem in _coreConfig.routing.rules.Where(r => balancerTagList.Contains(r.outboundTag + Global.BalancerTagSuffix))) + { + rulesItem.balancerTag = rulesItem.outboundTag + Global.BalancerTagSuffix; + rulesItem.outboundTag = null; } } } @@ -40,95 +51,94 @@ public partial class CoreConfigV2rayService { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenRoutingUserRule(RulesItem4Ray? rule, V2rayConfig v2rayConfig) + private void GenRoutingUserRule(RulesItem4Ray? userRule) { try { - if (rule == null) + if (userRule == null) { - return 0; + return; } - rule.outboundTag = await GenRoutingUserRuleOutbound(rule.outboundTag, v2rayConfig); + userRule.outboundTag = GenRoutingUserRuleOutbound(userRule.outboundTag ?? Global.ProxyTag); - if (rule.port.IsNullOrEmpty()) + if (userRule.port.IsNullOrEmpty()) { - rule.port = null; + userRule.port = null; } - if (rule.network.IsNullOrEmpty()) + if (userRule.network.IsNullOrEmpty()) { - rule.network = null; + userRule.network = null; } - if (rule.domain?.Count == 0) + if (userRule.domain?.Count == 0) { - rule.domain = null; + userRule.domain = null; } - if (rule.ip?.Count == 0) + if (userRule.ip?.Count == 0) { - rule.ip = null; + userRule.ip = null; } - if (rule.protocol?.Count == 0) + if (userRule.protocol?.Count == 0) { - rule.protocol = null; + userRule.protocol = null; } - if (rule.inboundTag?.Count == 0) + if (userRule.inboundTag?.Count == 0) { - rule.inboundTag = null; + userRule.inboundTag = null; } - if (rule.process?.Count == 0) + if (userRule.process?.Count == 0) { - rule.process = null; + userRule.process = null; } var hasDomainIp = false; - if (rule.domain?.Count > 0) + if (userRule.domain?.Count > 0) { - var it = JsonUtils.DeepCopy(rule); + var it = JsonUtils.DeepCopy(userRule); it.ip = null; it.process = null; it.type = "field"; for (var k = it.domain.Count - 1; k >= 0; k--) { - if (it.domain[k].StartsWith("#")) + if (it.domain[k].StartsWith('#')) { it.domain.RemoveAt(k); } it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ","); } - v2rayConfig.routing.rules.Add(it); + _coreConfig.routing.rules.Add(it); hasDomainIp = true; } - if (rule.ip?.Count > 0) + if (userRule.ip?.Count > 0) { - var it = JsonUtils.DeepCopy(rule); + var it = JsonUtils.DeepCopy(userRule); it.domain = null; it.process = null; it.type = "field"; - v2rayConfig.routing.rules.Add(it); + _coreConfig.routing.rules.Add(it); hasDomainIp = true; } - if (rule.process?.Count > 0) + if (userRule.process?.Count > 0) { - var it = JsonUtils.DeepCopy(rule); + var it = JsonUtils.DeepCopy(userRule); it.domain = null; it.ip = null; it.type = "field"; - v2rayConfig.routing.rules.Add(it); + _coreConfig.routing.rules.Add(it); hasDomainIp = true; } if (!hasDomainIp) { - if (rule.port.IsNotEmpty() - || rule.protocol?.Count > 0 - || rule.inboundTag?.Count > 0 - || rule.network != null + if (userRule.port.IsNotEmpty() + || userRule.protocol?.Count > 0 + || userRule.inboundTag?.Count > 0 + || userRule.network != null ) { - var it = JsonUtils.DeepCopy(rule); + var it = JsonUtils.DeepCopy(userRule); it.type = "field"; - v2rayConfig.routing.rules.Add(it); + _coreConfig.routing.rules.Add(it); } } } @@ -136,17 +146,16 @@ public partial class CoreConfigV2rayService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenRoutingUserRuleOutbound(string outboundTag, V2rayConfig v2rayConfig) + private string GenRoutingUserRuleOutbound(string outboundTag) { if (Global.OutboundTags.Contains(outboundTag)) { return outboundTag; } - var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag); + var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}"); if (node == null || (!Global.XraySupportConfigType.Contains(node.ConfigType) @@ -156,27 +165,20 @@ public partial class CoreConfigV2rayService } var tag = $"{node.IndexId}-{Global.ProxyTag}"; - if (v2rayConfig.outbounds.Any(p => p.tag == tag)) + if (_coreConfig.outbounds.Any(p => p.tag.StartsWith(tag))) { return tag; } - if (node.ConfigType.IsGroupType()) + var proxyOutbounds = new CoreConfigV2rayService(context with { Node = node, }).BuildAllProxyOutbounds(tag); + _coreConfig.outbounds.AddRange(proxyOutbounds); + if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1) { - var ret = await GenGroupOutbound(node, v2rayConfig, tag); - if (ret == 0) - { - return tag; - } - return Global.ProxyTag; + var multipleLoad = node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing; + GenObservatory(multipleLoad, tag); + GenBalancer(multipleLoad, tag); } - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(node, outbound); - outbound.tag = tag; - v2rayConfig.outbounds.Add(outbound); - - return outbound.tag; + return tag; } } diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayStatisticService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayStatisticService.cs index b2ec37b492..6364b7bd62 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayStatisticService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayStatisticService.cs @@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenStatistic(V2rayConfig v2rayConfig) + private void GenStatistic() { if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) { @@ -11,17 +11,17 @@ public partial class CoreConfigV2rayService Policy4Ray policyObj = new(); SystemPolicy4Ray policySystemSetting = new(); - v2rayConfig.stats = new Stats4Ray(); + _coreConfig.stats = new Stats4Ray(); apiObj.tag = tag; - v2rayConfig.metrics = apiObj; + _coreConfig.metrics = apiObj; policySystemSetting.statsOutboundDownlink = true; policySystemSetting.statsOutboundUplink = true; policyObj.system = policySystemSetting; - v2rayConfig.policy = policyObj; + _coreConfig.policy = policyObj; - if (!v2rayConfig.inbounds.Exists(item => item.tag == tag)) + if (!_coreConfig.inbounds.Exists(item => item.tag == tag)) { Inbounds4Ray apiInbound = new(); Inboundsettings4Ray apiInboundSettings = new(); @@ -31,10 +31,10 @@ public partial class CoreConfigV2rayService apiInbound.protocol = Global.InboundAPIProtocol; apiInboundSettings.address = Global.Loopback; apiInbound.settings = apiInboundSettings; - v2rayConfig.inbounds.Add(apiInbound); + _coreConfig.inbounds.Add(apiInbound); } - if (!v2rayConfig.routing.rules.Exists(item => item.outboundTag == tag)) + if (!_coreConfig.routing.rules.Exists(item => item.outboundTag == tag)) { RulesItem4Ray apiRoutingRule = new() { @@ -43,9 +43,8 @@ public partial class CoreConfigV2rayService type = "field" }; - v2rayConfig.routing.rules.Add(apiRoutingRule); + _coreConfig.routing.rules.Add(apiRoutingRule); } } - return await Task.FromResult(0); } } diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs index ba698c2072..303d94204b 100644 --- a/v2rayn/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs +++ b/v2rayn/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs @@ -247,11 +247,6 @@ public class AddServerViewModel : MyReactiveObject { serverName = SelectedSource.Address; } - if (!Utils.IsDomain(serverName)) - { - UpdateCertTip(ResUI.ServerNameMustBeValidDomain); - return; - } if (SelectedSource.Port > 0) { domain += $":{SelectedSource.Port}"; @@ -277,11 +272,6 @@ public class AddServerViewModel : MyReactiveObject { serverName = SelectedSource.Address; } - if (!Utils.IsDomain(serverName)) - { - UpdateCertTip(ResUI.ServerNameMustBeValidDomain); - return; - } if (SelectedSource.Port > 0) { domain += $":{SelectedSource.Port}"; diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 2d838189ec..8b4e7eb0b6 100644 --- a/v2rayn/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayn/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -801,7 +801,8 @@ public class ProfilesViewModel : MyReactiveObject if (blClipboard) { - var result = await CoreConfigHandler.GenerateClientConfig(item, null); + var context = await CoreConfigHandler.BuildCoreConfigContext(_config, item); + var result = await CoreConfigHandler.GenerateClientConfig(context, null); if (result.Success != true) { NoticeManager.Instance.Enqueue(result.Msg); @@ -824,7 +825,8 @@ public class ProfilesViewModel : MyReactiveObject { return; } - var result = await CoreConfigHandler.GenerateClientConfig(item, fileName); + var context = await CoreConfigHandler.BuildCoreConfigContext(_config, item); + var result = await CoreConfigHandler.GenerateClientConfig(context, fileName); if (result.Success != true) { NoticeManager.Instance.Enqueue(result.Msg); diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index 356698fb2d..477eca5dcd 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -32,7 +32,7 @@ IsCancel="True" /> - + - + - + - + + + + + + @@ -745,7 +785,7 @@ @@ -908,7 +948,7 @@ @@ -996,7 +1036,7 @@ Margin="{StaticResource Margin4}" HorizontalAlignment="Left" /> - + diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs b/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs index 250bbe784d..fdd097b2b2 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs +++ b/v2rayn/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs @@ -78,6 +78,7 @@ public partial class AddServerWindow : WindowBase cmbCoreType.IsEnabled = false; cmbFingerprint.IsEnabled = false; cmbFingerprint.SelectedValue = string.Empty; + gridFinalmask.IsVisible = false; cmbHeaderType8.ItemsSource = Global.TuicCongestionControls; break; @@ -95,6 +96,7 @@ public partial class AddServerWindow : WindowBase gridAnytls.IsVisible = true; lstStreamSecurity.Add(Global.StreamSecurityReality); cmbCoreType.IsEnabled = false; + gridFinalmask.IsVisible = false; break; } cmbStreamSecurity.ItemsSource = lstStreamSecurity; @@ -194,6 +196,8 @@ public partial class AddServerWindow : WindowBase this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Finalmask, v => v.txtFinalmask.Text).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); diff --git a/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml b/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml index 8c6b4a3535..e78e7f9ba9 100644 --- a/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml +++ b/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml @@ -60,6 +60,8 @@ + + @@ -928,12 +930,47 @@ Text="{x:Static resx:ResUI.TbPath}" /> + + + + + + + + + + + + + - + @@ -963,7 +1000,7 @@ @@ -1152,7 +1189,7 @@ @@ -1264,7 +1301,7 @@ Style="{StaticResource DefTextBox}" /> diff --git a/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs b/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs index 86d9cac193..4875afa5e6 100644 --- a/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs +++ b/v2rayn/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs @@ -73,6 +73,7 @@ public partial class AddServerWindow cmbCoreType.IsEnabled = false; cmbFingerprint.IsEnabled = false; cmbFingerprint.Text = string.Empty; + gridFinalmask.Visibility = Visibility.Collapsed; cmbHeaderType8.ItemsSource = Global.TuicCongestionControls; break; @@ -90,6 +91,7 @@ public partial class AddServerWindow gridAnytls.Visibility = Visibility.Visible; cmbCoreType.IsEnabled = false; lstStreamSecurity.Add(Global.StreamSecurityReality); + gridFinalmask.Visibility = Visibility.Collapsed; break; } cmbStreamSecurity.ItemsSource = lstStreamSecurity; @@ -190,6 +192,8 @@ public partial class AddServerWindow this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Finalmask, v => v.txtFinalmask.Text).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); diff --git a/v2rayng/V2rayNG/app/build.gradle.kts b/v2rayng/V2rayNG/app/build.gradle.kts index 1326bc4fb0..6bba04ca25 100644 --- a/v2rayng/V2rayNG/app/build.gradle.kts +++ b/v2rayng/V2rayNG/app/build.gradle.kts @@ -12,8 +12,8 @@ android { applicationId = "com.v2ray.ang" minSdk = 24 targetSdk = 36 - versionCode = 710 - versionName = "2.0.10" + versionCode = 711 + versionName = "2.0.11" multiDexEnabled = true val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';') diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt index 5413bef0a3..b8b48d7691 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt @@ -51,6 +51,7 @@ object AppConfig { const val PREF_CONFIRM_REMOVE = "pref_confirm_remove" const val PREF_START_SCAN_IMMEDIATE = "pref_start_scan_immediate" const val PREF_DOUBLE_COLUMN_DISPLAY = "pref_double_column_display" + const val PREF_GROUP_ALL_DISPLAY = "pref_group_all_display" const val PREF_LANGUAGE = "pref_language" const val PREF_UI_MODE_NIGHT = "pref_ui_mode_night" const val PREF_PREFER_IPV6 = "pref_prefer_ipv6" @@ -76,7 +77,6 @@ object AppConfig { /** Cache keys. */ const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id" - const val CACHE_KEYWORD_FILTER = "cache_keyword_filter" /** Protocol identifiers. */ const val PROTOCOL_FREEDOM = "freedom" diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt index d0f8f34377..ff41f884e3 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt @@ -11,6 +11,7 @@ import com.v2ray.ang.dto.ProfileItem import com.v2ray.ang.dto.SubscriptionCache import com.v2ray.ang.dto.SubscriptionItem import com.v2ray.ang.enums.EConfigType +import com.v2ray.ang.extension.isNotNullEmpty import com.v2ray.ang.fmt.CustomFmt import com.v2ray.ang.fmt.Hysteria2Fmt import com.v2ray.ang.fmt.ShadowsocksFmt @@ -237,23 +238,77 @@ object AngConfigManager { } val subItem = MmkvManager.decodeSubscription(subid) - var count = 0 + + // Parse all configs first (no I/O during parsing) + val configs = mutableListOf() servers.lines() .distinct() .reversed() .forEach { - val resId = parseConfig(it, subid, subItem, removedSelectedServer) - if (resId == 0) { - count++ + val config = parseConfig(it, subid, subItem) + if (config != null) { + configs.add(config) } } - return count + + // Batch save all parsed configs (only one serverList read/write) + if (configs.isNotEmpty()) { + val keys = batchSaveConfigs(configs, subid) + + // Handle removed selected server + removedSelectedServer?.let { removed -> + val matchKey = keys.find { key -> + val savedConfig = MmkvManager.decodeServerConfig(key) + savedConfig != null && + savedConfig.server == removed.server && + savedConfig.serverPort == removed.serverPort + } + matchKey?.let { MmkvManager.setSelectServer(it) } + } + } + + return configs.size } catch (e: Exception) { Log.e(AppConfig.TAG, "Failed to parse batch config", e) } return 0 } + /** + * Batch save configurations to reduce serverList read/write operations. + * Reads serverList once, saves all configs, then writes serverList once. + * + * @param configs The list of ProfileItem to save. + * @param subid The subscription ID. + * @return The list of generated keys. + */ + private fun batchSaveConfigs(configs: List, subid: String): List { + val keys = mutableListOf() + + // Read serverList once + val serverList = MmkvManager.decodeServerList(subid) + var needSetSelected = MmkvManager.getSelectServer().isNullOrBlank() + + configs.forEach { config -> + val key = Utils.getUuid() + // Save profile directly without updating serverList + MmkvManager.encodeProfileDirect(key, JsonUtil.toJson(config)) + + if (!serverList.contains(key)) { + serverList.add(0, key) + if (needSetSelected) { + MmkvManager.setSelectServer(key) + needSetSelected = false + } + } + keys.add(key) + } + + // Write serverList once + MmkvManager.encodeServerList(serverList, subid) + return keys + } + /** * Parses a custom configuration server. * @@ -319,22 +374,21 @@ object AngConfigManager { /** * Parses the configuration from a QR code or string. + * Only parses and returns ProfileItem, does not save. * * @param str The configuration string. * @param subid The subscription ID. * @param subItem The subscription item. - * @param removedSelectedServer The removed selected server. - * @return The result code. + * @return The parsed ProfileItem or null if parsing fails or filtered out. */ private fun parseConfig( str: String?, subid: String, - subItem: SubscriptionItem?, - removedSelectedServer: ProfileItem? - ): Int { + subItem: SubscriptionItem? + ): ProfileItem? { try { if (str == null || TextUtils.isEmpty(str)) { - return R.string.toast_none_data + return null } val config = if (str.startsWith(EConfigType.VMESS.protocolScheme)) { @@ -356,28 +410,24 @@ object AngConfigManager { } if (config == null) { - return R.string.toast_incorrect_protocol + return null } - //filter - if (subItem?.filter != null && subItem.filter?.isNotEmpty() == true && config.remarks.isNotEmpty()) { - val matched = Regex(pattern = subItem.filter ?: "") + + // Apply filter + if (subItem?.filter.isNotNullEmpty() && config.remarks.isNotNullEmpty()) { + val matched = Regex(pattern = subItem?.filter.orEmpty()) .containsMatchIn(input = config.remarks) - if (!matched) return -1 + if (!matched) return null } config.subscriptionId = subid config.description = generateDescription(config) - val guid = MmkvManager.encodeServerConfig("", config) - if (removedSelectedServer != null && - config.server == removedSelectedServer.server && config.serverPort == removedSelectedServer.serverPort - ) { - MmkvManager.setSelectServer(guid) - } + + return config } catch (e: Exception) { Log.e(AppConfig.TAG, "Failed to parse config", e) - return -1 + return null } - return 0 } /** diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/MmkvManager.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/MmkvManager.kt index c05b066479..9dbbfa4db8 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/MmkvManager.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/MmkvManager.kt @@ -225,6 +225,16 @@ object MmkvManager { return key } + /** + * Encodes the server configuration directly without updating serverList. + * + * @param key The server GUID. + * @param configJson The server configuration JSON string. + */ + fun encodeProfileDirect(key: String, configJson: String) { + profileFullStorage.encode(key, configJson) + } + /** * Removes the server configuration. * diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/helper/MmkvPreferenceDataStore.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/helper/MmkvPreferenceDataStore.kt index 4316af3eb1..ffc075d379 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/helper/MmkvPreferenceDataStore.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/helper/MmkvPreferenceDataStore.kt @@ -79,5 +79,6 @@ class MmkvPreferenceDataStore : PreferenceDataStore() { } // Notify listeners that require service restart or reinit SettingsChangeManager.makeRestartService() + SettingsChangeManager.makeSetupGroupTab() } } \ No newline at end of file diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayTestService.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayTestService.kt index 713a7f9d8a..ec7d560268 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayTestService.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayTestService.kt @@ -6,7 +6,7 @@ import android.os.IBinder import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_CANCEL -import com.v2ray.ang.extension.serializable +import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.V2RayNativeManager import com.v2ray.ang.util.MessageUtil import java.util.Collections @@ -54,8 +54,14 @@ class V2RayTestService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.getIntExtra("key", 0)) { MSG_MEASURE_CONFIG -> { - val guidsList = intent.serializable>("content") - if (guidsList != null && guidsList.isNotEmpty()) { + val subscriptionId = intent.getStringExtra("content").orEmpty() + val guidsList = if (subscriptionId.isEmpty()) { + MmkvManager.decodeAllServerList() + } else { + MmkvManager.decodeServerList(subscriptionId) + } + + if (guidsList.isNotEmpty()) { lateinit var worker: RealPingWorkerService worker = RealPingWorkerService(this, guidsList) { status -> // notify UI and remove the worker from active list when finished diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt index 6b17f25ba8..242dc638ae 100644 --- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt +++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt @@ -37,8 +37,6 @@ import java.util.Collections class MainViewModel(application: Application) : AndroidViewModel(application) { private var serverList = mutableListOf() // MmkvManager.decodeServerList() var subscriptionId: String = MmkvManager.decodeSettingsString(AppConfig.CACHE_SUBSCRIPTION_ID, "").orEmpty() - - //var keywordFilter: String = MmkvManager.MmkvManager.decodeSettingsString(AppConfig.CACHE_KEYWORD_FILTER, "")?:"" var keywordFilter = "" val serversCache = mutableListOf() val isRunning by lazy { MutableLiveData() } @@ -149,6 +147,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { @Synchronized fun updateCache() { serversCache.clear() + val kw = keywordFilter.trim().lowercase() for (guid in serverList) { val profile = MmkvManager.decodeServerConfig(guid) ?: continue // var profile = MmkvManager.decodeProfileConfig(guid) @@ -167,8 +166,16 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { // if (subscriptionId.isNotEmpty() && subscriptionId != profile.subscriptionId) { // continue // } + if (kw.isEmpty()) { + serversCache.add(ServersCache(guid, profile)) + continue + } - if (keywordFilter.isEmpty() || profile.remarks.lowercase().contains(keywordFilter.lowercase())) { + val remarks = profile.remarks.lowercase() + val description = profile.description.orEmpty().lowercase() + val server = profile.server.orEmpty().lowercase() + + if (remarks.contains(kw) || description.contains(kw) || server.contains(kw)) { serversCache.add(ServersCache(guid, profile)) } } @@ -240,13 +247,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { MmkvManager.clearAllTestDelayResults(serversCache.map { it.guid }.toList()) updateListAction.value = -1 - val serversCopy = serversCache.toList() viewModelScope.launch(Dispatchers.Default) { - val guids = ArrayList(serversCopy.map { it.guid }) - if (guids.isEmpty()) { + if (serversCache.isEmpty()) { return@launch } - MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG, guids) + MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG, subscriptionId) } } @@ -283,7 +288,9 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } val groups = mutableListOf() - if (subscriptions.count() > 1) { + if (subscriptions.size > 1 + && MmkvManager.decodeSettingsBool(AppConfig.PREF_GROUP_ALL_DISPLAY) + ) { groups.add( GroupMapItem( id = "", @@ -430,7 +437,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { return } keywordFilter = keyword - MmkvManager.encodeSettings(AppConfig.CACHE_KEYWORD_FILTER, keywordFilter) reloadServerList() } diff --git a/v2rayng/V2rayNG/app/src/main/res/values-ar/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-ar/strings.xml index 277b0cbc59..e33e827453 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-ar/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-ar/strings.xml @@ -234,6 +234,8 @@ Enable double column display The profile list is displayed in double columns, allowing more content to be displayed on the screen. You need to restart the application to take effect. + Enable show all groups + Add an extra "All Tabs" page ملاحظات ملاحظات التحسينات أو الأخطاء إلى GitHub diff --git a/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml index 863c9a4a77..ab3870c14f 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-bn/strings.xml @@ -234,6 +234,8 @@ Enable double column display The profile list is displayed in double columns, allowing more content to be displayed on the screen. You need to restart the application to take effect. + Enable show all groups + Add an extra "All Tabs" page মতামত মতামত উন্নয়ন বা বাগগুলি GitHub-এ পাঠান diff --git a/v2rayng/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml index 953f55bb98..008cec572c 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml @@ -234,6 +234,8 @@ ره وندن نشووݩ داڌن دو سۊتۊنی نومگه نمایه یل من دو سۊتۊن نشووݩ داڌه ابۊن وو چینۉ ترین موئتوا بیشتری ن سیل کۊنین. سی ره وستن، وا برنومه ن ز نۊ ره ونین. + Enable show all groups + Add an extra "All Tabs" page فشناڌن منشڌ فشناڌن منشڌ یا گوزارش موشکلا من Github diff --git a/v2rayng/V2rayNG/app/src/main/res/values-fa/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-fa/strings.xml index 6bc10901ae..75278311fb 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-fa/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-fa/strings.xml @@ -232,6 +232,8 @@ فعال کردن نمایش دو ستون لیست نمایه در دو ستون نمایش داده می شود و امکان نمایش محتوای بیشتری را بر روی صفحه نمایش می دهد. برای اجرا باید برنامه را مجددا راه اندازی کنید. + Enable show all groups + Add an extra "All Tabs" page بازخورد بازخورد یا گزارش اشکالات در گیت‌ هاب diff --git a/v2rayng/V2rayNG/app/src/main/res/values-ru/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-ru/strings.xml index 3d9adecbc6..d6807862ed 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-ru/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-ru/strings.xml @@ -233,6 +233,8 @@ Профили в два столбца Список профилей отображается двумя столбцами, что позволяет показать больше информации на экране. Требуется перезапуск приложения. + Enable show all groups + Add an extra "All Tabs" page Обратная связь Предложить улучшение или сообщить об ошибке на GitHub diff --git a/v2rayng/V2rayNG/app/src/main/res/values-vi/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-vi/strings.xml index 99779ed83e..745d71abd3 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-vi/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-vi/strings.xml @@ -234,6 +234,8 @@ Enable double column display The profile list is displayed in double columns, allowing more content to be displayed on the screen. You need to restart the application to take effect. + Enable show all groups + Add an extra "All Tabs" page Phản hồi lỗi Phản hồi cải tiến hoặc lỗi lên GitHub diff --git a/v2rayng/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml index 87ff259c5a..77e803600e 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -231,6 +231,9 @@ 启用双列显示 配置列表以双列显示,允许在屏幕上显示更多内容。需要重启应用生效。 + 启用显示所有组 + 添加一个额外的“所有选项卡”页面 + 反馈 反馈改进或漏洞至 GitHub diff --git a/v2rayng/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index 2fac4392ec..70f51cddea 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -232,6 +232,9 @@ 啟用雙列顯示 設定檔清單以雙列顯示,允許在螢幕上顯示更多內容。需要重啟應用生效。 + 啟用顯示所有群組 + 新增一個額外的「所有選項卡」頁面 + 意見回饋 前往 GitHub 回報錯誤 diff --git a/v2rayng/V2rayNG/app/src/main/res/values/strings.xml b/v2rayng/V2rayNG/app/src/main/res/values/strings.xml index 8661270746..5e4ddc8bfc 100644 --- a/v2rayng/V2rayNG/app/src/main/res/values/strings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/values/strings.xml @@ -237,6 +237,9 @@ Enable double column display The profile list is displayed in double columns, allowing more content to be displayed on the screen. You need to restart the application to take effect. + Enable show all groups + Add an extra "All Tabs" page + Feedback Feedback enhancements or bugs to GitHub diff --git a/v2rayng/V2rayNG/app/src/main/res/xml/pref_settings.xml b/v2rayng/V2rayNG/app/src/main/res/xml/pref_settings.xml index a79a29f1a8..d82374a6ff 100644 --- a/v2rayng/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/v2rayng/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -23,6 +23,11 @@ android:summary="@string/summary_pref_double_column_display" android:title="@string/title_pref_double_column_display" /> + +