Compare commits

..

No commits in common. "master" and "7.16.8" have entirely different histories.

51 changed files with 486 additions and 711 deletions

View file

@ -37,11 +37,11 @@ jobs:
- name: Build - name: Build
run: | run: |
cd v2rayN cd v2rayN
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64 dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -p:SelfContained=false -p:EnableWindowsTargeting=true -o $OutputPath64
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64 dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-arm64 -p:SelfContained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64Sc dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64Sc
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64 dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=false -p:EnableWindowsTargeting=true -o $OutputPath64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64 dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64Sc dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64Sc

View file

@ -1,11 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# ====== Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS ====== # == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ==
if [[ -r /etc/os-release ]]; then if [[ -r /etc/os-release ]]; then
. /etc/os-release . /etc/os-release
case "$ID" in case "$ID" in
rhel|rocky|almalinux|fedora|centos) rhel|rocky|almalinux|fedora|centos|ubuntu|debian)
echo "[OK] Detected supported system: $NAME $VERSION_ID" echo "[OK] Detected supported system: $NAME $VERSION_ID"
;; ;;
*) *)
@ -30,7 +30,7 @@ echo "[INFO] Detected kernel version: $KERNEL_FULL"
if (( KERNEL_MAJOR < MIN_KERNEL_MAJOR )) || { (( KERNEL_MAJOR == MIN_KERNEL_MAJOR )) && (( KERNEL_MINOR < MIN_KERNEL_MINOR )); }; then if (( KERNEL_MAJOR < MIN_KERNEL_MAJOR )) || { (( KERNEL_MAJOR == MIN_KERNEL_MAJOR )) && (( KERNEL_MINOR < MIN_KERNEL_MINOR )); }; then
echo "[ERROR] Kernel $KERNEL_FULL is too old. Requires Linux >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}." echo "[ERROR] Kernel $KERNEL_FULL is too old. Requires Linux >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}."
echo "Please upgrade your system or use a newer container (e.g. Fedora 42+, RHEL 10+)." echo "Please upgrade your system or use a newer container (e.g. Fedora 42+, RHEL 10+, Debian 13+)."
exit 1 exit 1
fi fi
@ -80,6 +80,7 @@ host_arch="$(uname -m)"
install_ok=0 install_ok=0
case "$ID" in case "$ID" in
# ------------------------------ RHEL family (UNCHANGED) ------------------------------
rhel|rocky|almalinux|centos) rhel|rocky|almalinux|centos)
if command -v dnf >/dev/null 2>&1; then if command -v dnf >/dev/null 2>&1; then
sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \ sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
@ -91,7 +92,58 @@ case "$ID" in
install_ok=1 install_ok=1
fi fi
;; ;;
*) # ------------------------------ Ubuntu ----------------------------------------------
ubuntu)
sudo apt-get update
# Ensure 'universe' (Ubuntu) to get 'rpm'
if ! apt-cache policy | grep -q '^500 .*ubuntu.com/ubuntu.* universe'; then
sudo apt-get -y install software-properties-common || true
sudo add-apt-repository -y universe || true
sudo apt-get update
fi
# Base tools + rpm (provides rpmbuild)
sudo apt-get -y install curl unzip tar rsync rpm || true
# Cross-arch binutils so strip matches target arch + objdump for brp scripts
sudo apt-get -y install binutils binutils-x86-64-linux-gnu binutils-aarch64-linux-gnu || true
# rpmbuild presence check
if ! command -v rpmbuild >/dev/null 2>&1; then
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
echo " Please ensure the 'rpm' package is available from your repos (universe on Ubuntu)."
exit 1
fi
# .NET SDK 8 (best effort via apt)
if ! command -v dotnet >/dev/null 2>&1; then
sudo apt-get -y install dotnet-sdk-8.0 || true
sudo apt-get -y install dotnet-sdk-8 || true
sudo apt-get -y install dotnet-sdk || true
fi
install_ok=1
;;
# ------------------------------ Debian (KEEP, with local dotnet install) ------------
debian)
sudo apt-get update
# Base tools + rpm (provides rpmbuild on Debian) + objdump/strip
sudo apt-get -y install curl unzip tar rsync rpm binutils || true
# rpmbuild presence check
if ! command -v rpmbuild >/dev/null 2>&1; then
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
echo " Please ensure 'rpm' is available from Debian repos."
exit 1
fi
# Try apt for dotnet; fallback to official installer into $HOME/.dotnet
if ! command -v dotnet >/dev/null 2>&1; then
echo "[INFO] 'dotnet' not found. Installing .NET 8 SDK locally to \$HOME/.dotnet ..."
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$tmp/dotnet-install.sh"
bash "$tmp/dotnet-install.sh" --channel 8.0 --install-dir "$HOME/.dotnet"
export PATH="$HOME/.dotnet:$HOME/.dotnet/tools:$PATH"
export DOTNET_ROOT="$HOME/.dotnet"
if ! command -v dotnet >/dev/null 2>&1; then
echo "[ERROR] dotnet installation failed."
exit 1
fi
fi
install_ok=1
;; ;;
esac esac
@ -102,7 +154,7 @@ fi
command -v curl >/dev/null command -v curl >/dev/null
# Root directory # Root directory = the script's location
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR" cd "$SCRIPT_DIR"
@ -112,14 +164,14 @@ if [[ -f .gitmodules ]]; then
git submodule update --init --recursive || true git submodule update --init --recursive || true
fi fi
# Locate project # ===== Locate project ================================================================
PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj" PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj"
if [[ ! -f "$PROJECT" ]]; then if [[ ! -f "$PROJECT" ]]; then
PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)" PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)"
fi fi
[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; } [[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; }
# Resolve GUI version & auto checkout # ===== Resolve GUI version & auto checkout ============================================
VERSION="" VERSION=""
choose_channel() { choose_channel() {
@ -339,6 +391,22 @@ download_singbox() {
install -Dm755 "$bin" "$outdir/sing-box" install -Dm755 "$bin" "$outdir/sing-box"
} }
# ---- NEW: download_mihomo (REQUIRED in --netcore mode) ----
download_mihomo() {
# Download mihomo into outroot/bin/mihomo/mihomo
local outroot="$1"
local url=""
if [[ "$RID_DIR" == "linux-arm64" ]]; then
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64/bin/mihomo/mihomo"
else
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64/bin/mihomo/mihomo"
fi
echo "[+] Download mihomo: $url"
mkdir -p "$outroot/bin/mihomo"
curl -fL "$url" -o "$outroot/bin/mihomo/mihomo"
chmod +x "$outroot/bin/mihomo/mihomo" || true
}
# Move geo files to a unified path: outroot/bin # Move geo files to a unified path: outroot/bin
unify_geo_layout() { unify_geo_layout() {
local outroot="$1" local outroot="$1"
@ -423,7 +491,8 @@ download_v2rayn_bundle() {
fi fi
rm -f "$outroot/v2rayn.zip" 2>/dev/null || true rm -f "$outroot/v2rayn.zip" 2>/dev/null || true
find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true # keep mihomo
# find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
local nested_dir local nested_dir
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)" nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
@ -534,7 +603,7 @@ build_for_arch() {
fi fi
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)" download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
# ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ---- # ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ----
# download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)" download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)"
fi fi
# Tarball # Tarball

View file

@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.17.0</Version> <Version>7.16.8</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -14,14 +14,14 @@
<PackageVersion Include="Downloader" Version="4.0.3" /> <PackageVersion Include="Downloader" Version="4.0.3" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" /> <PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" /> <PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" /> <PackageVersion Include="MessageBox.Avalonia" Version="3.3.1" />
<PackageVersion Include="QRCoder" Version="1.7.0" /> <PackageVersion Include="QRCoder" Version="1.7.0" />
<PackageVersion Include="ReactiveUI" Version="22.3.1" /> <PackageVersion Include="ReactiveUI" Version="22.3.1" />
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" /> <PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="22.3.1" /> <PackageVersion Include="ReactiveUI.WPF" Version="22.3.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.3.7.2" /> <PackageVersion Include="Semi.Avalonia" Version="11.3.7.1" />
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" /> <PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.2" /> <PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.1" />
<PackageVersion Include="NLog" Version="6.0.7" /> <PackageVersion Include="NLog" Version="6.0.7" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" /> <PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" /> <PackageVersion Include="TaskScheduler" Version="2.12.2" />

View file

@ -6,17 +6,17 @@ public static class Extension
{ {
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value) public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value)
{ {
return string.IsNullOrWhiteSpace(value) || string.IsNullOrEmpty(value); return string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value);
}
public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value)
{
return string.IsNullOrWhiteSpace(value);
} }
public static bool IsNotEmpty([NotNullWhen(false)] this string? value) public static bool IsNotEmpty([NotNullWhen(false)] this string? value)
{ {
return !string.IsNullOrWhiteSpace(value); return !string.IsNullOrEmpty(value);
}
public static string? NullIfEmpty(this string? value)
{
return string.IsNullOrWhiteSpace(value) ? null : value;
} }
public static bool BeginWithAny(this string s, IEnumerable<char> chars) public static bool BeginWithAny(this string s, IEnumerable<char> chars)

View file

@ -88,7 +88,6 @@ public class Global
public const string SingboxLocalDNSTag = "local_local"; public const string SingboxLocalDNSTag = "local_local";
public const string SingboxHostsDNSTag = "hosts_dns"; public const string SingboxHostsDNSTag = "hosts_dns";
public const string SingboxFakeDNSTag = "fake_dns"; public const string SingboxFakeDNSTag = "fake_dns";
public const string SingboxEchDNSTag = "ech_dns";
public static readonly List<string> IEProxyProtocols = public static readonly List<string> IEProxyProtocols =
[ [
@ -108,9 +107,7 @@ public class Global
]; ];
public static readonly List<string> SubConvertConfig = public static readonly List<string> SubConvertConfig =
[ [@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"];
@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"
];
public static readonly List<string> SubConvertTargets = public static readonly List<string> SubConvertTargets =
[ [
@ -145,11 +142,11 @@ public class Global
]; ];
public static readonly List<string> SingboxRulesetSources = public static readonly List<string> SingboxRulesetSources =
[ [
"", "",
@"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/sing-box/rule-set-{0}/{1}.srs", @"https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/sing-box/rule-set-{0}/{1}.srs",
@"https://raw.githubusercontent.com/chocolate4u/Iran-sing-box-rules/rule-set/{1}.srs" @"https://raw.githubusercontent.com/chocolate4u/Iran-sing-box-rules/rule-set/{1}.srs"
]; ];
public static readonly List<string> RoutingRulesSources = public static readonly List<string> RoutingRulesSources =
[ [
@ -629,13 +626,5 @@ public class Global
"" ""
]; ];
public static readonly List<string> EchForceQuerys =
[
"none",
"half",
"full",
""
];
#endregion const #endregion const
} }

View file

@ -253,8 +253,6 @@ public static class ConfigHandler
item.Extra = profileItem.Extra; item.Extra = profileItem.Extra;
item.MuxEnabled = profileItem.MuxEnabled; item.MuxEnabled = profileItem.MuxEnabled;
item.Cert = profileItem.Cert; item.Cert = profileItem.Cert;
item.EchConfigList = profileItem.EchConfigList;
item.EchForceQuery = profileItem.EchForceQuery;
} }
var ret = item.ConfigType switch var ret = item.ConfigType switch
@ -1275,7 +1273,7 @@ public static class ConfigHandler
} }
else if (node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0) else if (node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)
{ {
var preCoreType = AppManager.Instance.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray; var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
itemSocks = new ProfileItem() itemSocks = new ProfileItem()
{ {
CoreType = preCoreType, CoreType = preCoreType,

View file

@ -70,10 +70,6 @@ public class BaseFmt
} }
ToUriQueryAllowInsecure(item, ref dicQuery); ToUriQueryAllowInsecure(item, ref dicQuery);
} }
if (item.EchConfigList.IsNotEmpty())
{
dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList));
}
dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp)); dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp));
@ -213,7 +209,6 @@ public class BaseFmt
item.ShortId = GetQueryDecoded(query, "sid"); item.ShortId = GetQueryDecoded(query, "sid");
item.SpiderX = GetQueryDecoded(query, "spx"); item.SpiderX = GetQueryDecoded(query, "spx");
item.Mldsa65Verify = GetQueryDecoded(query, "pqv"); item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
item.EchConfigList = GetQueryDecoded(query, "ech");
if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1")) if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1"))
{ {

View file

@ -193,6 +193,19 @@ public class ActionPrecheckManager
} }
} }
// ws with tls, tls alpn should contain "http/1.1" in xray core
// rfc6455
// https://github.com/XTLS/Xray-core/blob/81f8f398c7b2b845853b1e85087c6122acc6db0b/transport/internet/tls/tls.go#L95-L116
if (item.Network == nameof(ETransport.ws)
&& item.StreamSecurity == Global.StreamSecurity)
{
var alpnList = Utils.String2List(item.Alpn) ?? [];
if (alpnList.Count > 0 && !alpnList.Contains("http/1.1"))
{
errors.Add(ResUI.AlpnMustContainHttp11ForWsTls);
}
}
return errors; return errors;
} }
@ -214,10 +227,9 @@ public class ActionPrecheckManager
return errors; return errors;
} }
var childIds = new List<string>(); var childIds = Utils.String2List(group.ChildItems) ?? [];
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group); var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
childIds.AddRange(subItems.Select(p => p.IndexId)); childIds.AddRange(subItems.Select(p => p.IndexId));
childIds.AddRange(Utils.String2List(group.ChildItems));
foreach (var child in childIds) foreach (var child in childIds)
{ {

View file

@ -31,23 +31,6 @@ public sealed class AppManager
public string LinuxSudoPwd { get; set; } public string LinuxSudoPwd { get; set; }
public bool ShowInTaskbar { get; set; }
public ECoreType RunningCoreType { get; set; }
public bool IsRunningCore(ECoreType type)
{
switch (type)
{
case ECoreType.Xray when RunningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5:
case ECoreType.sing_box when RunningCoreType is ECoreType.sing_box or ECoreType.mihomo:
return true;
default:
return false;
}
}
#endregion Property #endregion Property
#region App #region App

View file

@ -125,7 +125,7 @@ public sealed class CoreInfoManager
new CoreInfo new CoreInfo
{ {
CoreType = ECoreType.mihomo, CoreType = ECoreType.mihomo,
CoreExes = GetMihomoCoreExes(), CoreExes = ["mihomo-windows-amd64-v1", "mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-linux-amd64", "clash", "mihomo"],
Arguments = "-f {0}" + PortableMode(), Arguments = "-f {0}" + PortableMode(),
Url = GetCoreUrl(ECoreType.mihomo), Url = GetCoreUrl(ECoreType.mihomo),
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl), ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl),
@ -248,34 +248,4 @@ public sealed class CoreInfoManager
{ {
return $"{Global.GithubUrl}/{Global.CoreUrls[eCoreType]}/releases"; return $"{Global.GithubUrl}/{Global.CoreUrls[eCoreType]}/releases";
} }
private static List<string>? GetMihomoCoreExes()
{
var names = new List<string>();
if (Utils.IsWindows())
{
names.Add("mihomo-windows-amd64-v1");
names.Add("mihomo-windows-amd64-compatible");
names.Add("mihomo-windows-amd64");
names.Add("mihomo-windows-arm64");
}
else if (Utils.IsLinux())
{
names.Add("mihomo-linux-amd64-v1");
names.Add("mihomo-linux-amd64");
names.Add("mihomo-linux-arm64");
}
else if (Utils.IsMacOS())
{
names.Add("mihomo-darwin-amd64-v1");
names.Add("mihomo-darwin-amd64");
names.Add("mihomo-darwin-arm64");
}
names.Add("clash");
names.Add("mihomo");
return names;
}
} }

View file

@ -167,7 +167,7 @@ public class CoreManager
private async Task CoreStart(ProfileItem node) private async Task CoreStart(ProfileItem node)
{ {
var coreType = AppManager.Instance.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType); var coreType = _config.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog; var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;

View file

@ -230,10 +230,9 @@ public class ProfileGroupItemManager
{ {
return (new List<ProfileItem>(), profileGroupItem); return (new List<ProfileItem>(), profileGroupItem);
} }
var items = await GetChildProfileItems(profileGroupItem);
var items = new List<ProfileItem>(); var subItems = await GetSubChildProfileItems(profileGroupItem);
items.AddRange(await GetSubChildProfileItems(profileGroupItem)); items.AddRange(subItems);
items.AddRange(await GetChildProfileItems(profileGroupItem));
return (items, profileGroupItem); return (items, profileGroupItem);
} }
@ -317,72 +316,5 @@ public class ProfileGroupItemManager
return childAddresses; return childAddresses;
} }
public static async Task<HashSet<string>> GetAllChildEchQuerySni(string indexId)
{
// include grand children
var childAddresses = new HashSet<string>();
if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null)
{
return childAddresses;
}
if (groupItem.SubChildItems.IsNotEmpty())
{
var subItems = await GetSubChildProfileItems(groupItem);
foreach (var childNode in subItems)
{
if (childNode.EchConfigList.IsNullOrEmpty())
{
continue;
}
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);
}
}
}
var childIds = Utils.String2List(groupItem.ChildItems) ?? [];
foreach (var childId in childIds)
{
var childNode = await AppManager.Instance.GetProfileItem(childId);
if (childNode == null)
{
continue;
}
if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty())
{
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.IndexId);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
}
}
return childAddresses;
}
#endregion Helper #endregion Helper
} }

View file

@ -8,6 +8,21 @@ public class Config
public string IndexId { get; set; } public string IndexId { get; set; }
public string SubIndexId { get; set; } public string SubIndexId { get; set; }
public ECoreType RunningCoreType { get; set; }
public bool IsRunningCore(ECoreType type)
{
switch (type)
{
case ECoreType.Xray when RunningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5:
case ECoreType.sing_box when RunningCoreType is ECoreType.sing_box or ECoreType.mihomo:
return true;
default:
return false;
}
}
#endregion property #endregion property
#region other entities #region other entities

View file

@ -100,6 +100,7 @@ public class UIItem
public bool DoubleClick2Activate { get; set; } public bool DoubleClick2Activate { get; set; }
public bool AutoHideStartup { get; set; } public bool AutoHideStartup { get; set; }
public bool Hide2TrayWhenClose { get; set; } public bool Hide2TrayWhenClose { get; set; }
public bool ShowInTaskbar { get; set; }
public bool MacOSShowInDock { get; set; } public bool MacOSShowInDock { get; set; }
public List<ColumnItem> MainColumnItem { get; set; } public List<ColumnItem> MainColumnItem { get; set; }
public List<WindowSizeItem> WindowSizeItem { get; set; } public List<WindowSizeItem> WindowSizeItem { get; set; }

View file

@ -161,6 +161,4 @@ public class ProfileItem : ReactiveObject
public string Extra { get; set; } public string Extra { get; set; }
public bool? MuxEnabled { get; set; } public bool? MuxEnabled { get; set; }
public string Cert { get; set; } public string Cert { get; set; }
public string EchConfigList { get; set; }
public string EchForceQuery { get; set; }
} }

View file

@ -182,14 +182,6 @@ public class Tls4Sbox
public string? fragment_fallback_delay { get; set; } public string? fragment_fallback_delay { get; set; }
public bool? record_fragment { get; set; } public bool? record_fragment { get; set; }
public List<string>? certificate { get; set; } public List<string>? certificate { get; set; }
public Ech4Sbox? ech { get; set; }
}
public class Ech4Sbox
{
public bool enabled { get; set; }
public List<string>? config { get; set; }
public string? query_server_name { get; set; }
} }
public class Multiplex4Sbox public class Multiplex4Sbox

View file

@ -356,8 +356,6 @@ public class TlsSettings4Ray
public string? mldsa65Verify { get; set; } public string? mldsa65Verify { get; set; }
public List<CertificateSettings4Ray>? certificates { get; set; } public List<CertificateSettings4Ray>? certificates { get; set; }
public bool? disableSystemRoot { get; set; } public bool? disableSystemRoot { get; set; }
public string? echConfigList { get; set; }
public string? echForceQuery { get; set; }
} }
public class CertificateSettings4Ray public class CertificateSettings4Ray

View file

@ -78,6 +78,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 ALPN must contain &apos;http/1.1&apos; when using WebSocket with TLS. 的本地化字符串。
/// </summary>
public static string AlpnMustContainHttp11ForWsTls {
get {
return ResourceManager.GetString("AlpnMustContainHttp11ForWsTls", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Export share link to clipboard successfully 的本地化字符串。 /// 查找类似 Export share link to clipboard successfully 的本地化字符串。
/// </summary> /// </summary>
@ -2790,24 +2799,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 EchConfigList 的本地化字符串。
/// </summary>
public static string TbEchConfigList {
get {
return ResourceManager.GetString("TbEchConfigList", resourceCulture);
}
}
/// <summary>
/// 查找类似 EchForceQuery 的本地化字符串。
/// </summary>
public static string TbEchForceQuery {
get {
return ResourceManager.GetString("TbEchForceQuery", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Edit 的本地化字符串。 /// 查找类似 Edit 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1641,10 +1641,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value> <value>Configuration Item 2, Select and add from self-built</value>
</data> </data>
<data name="TbEchConfigList" xml:space="preserve"> <data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>EchConfigList</value> <value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -1638,10 +1638,7 @@ Si un certificat auto-signé est utilisé ou si le système contient une CA non
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>Élément de config 2 : choisir et ajouter depuis self-hosted</value> <value>Élément de config 2 : choisir et ajouter depuis self-hosted</value>
</data> </data>
<data name="TbEchConfigList" xml:space="preserve"> <data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>EchConfigList</value> <value>Avec WebSocket et TLS, lALPN doit inclure http/1.1.</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -1641,10 +1641,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value> <value>Configuration Item 2, Select and add from self-built</value>
</data> </data>
<data name="TbEchConfigList" xml:space="preserve"> <data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>EchConfigList</value> <value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -1641,10 +1641,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value> <value>Configuration Item 2, Select and add from self-built</value>
</data> </data>
<data name="TbEchConfigList" xml:space="preserve"> <data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>EchConfigList</value> <value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -1641,10 +1641,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>Configuration Item 2, Select and add from self-built</value> <value>Configuration Item 2, Select and add from self-built</value>
</data> </data>
<data name="TbEchConfigList" xml:space="preserve"> <data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>EchConfigList</value> <value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -1638,10 +1638,7 @@
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>子配置项二,从自建中选择添加</value> <value>子配置项二,从自建中选择添加</value>
</data> </data>
<data name="TbEchConfigList" xml:space="preserve"> <data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>EchConfigList</value> <value>使用 WebSocket+TLS 时ALPN 必须包含 'http/1.1'。</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -1638,10 +1638,7 @@
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>子配置項二,從自建中選擇新增</value> <value>子配置項二,從自建中選擇新增</value>
</data> </data>
<data name="TbEchConfigList" xml:space="preserve"> <data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
<value>EchConfigList</value> <value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -371,7 +371,7 @@ public partial class CoreConfigSingboxService(Config config)
await GenRouting(singboxConfig); await GenRouting(singboxConfig);
await GenExperimental(singboxConfig); await GenExperimental(singboxConfig);
await GenDns(parentNode, singboxConfig); await GenDns(null, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig); await ConvertGeo2Ruleset(singboxConfig);
ret.Success = true; ret.Success = true;
@ -428,7 +428,7 @@ public partial class CoreConfigSingboxService(Config config)
await GenRouting(singboxConfig); await GenRouting(singboxConfig);
await GenExperimental(singboxConfig); await GenExperimental(singboxConfig);
await GenDns(parentNode, singboxConfig); await GenDns(null, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig); await ConvertGeo2Ruleset(singboxConfig);
ret.Success = true; ret.Success = true;

View file

@ -13,8 +13,8 @@ public partial class CoreConfigSingboxService
} }
var simpleDNSItem = _config.SimpleDNSItem; var simpleDNSItem = _config.SimpleDNSItem;
await GenDnsServers(node, singboxConfig, simpleDNSItem); await GenDnsServers(singboxConfig, simpleDNSItem);
await GenDnsRules(node, singboxConfig, simpleDNSItem); await GenDnsRules(singboxConfig, simpleDNSItem);
singboxConfig.dns ??= new Dns4Sbox(); singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.independent_cache = true; singboxConfig.dns.independent_cache = true;
@ -52,7 +52,7 @@ public partial class CoreConfigSingboxService
return 0; return 0;
} }
private async Task<int> GenDnsServers(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) private async Task<int> GenDnsServers(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
{ {
var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem); var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem);
@ -133,29 +133,6 @@ public partial class CoreConfigSingboxService
singboxConfig.dns.servers.Add(fakeip); singboxConfig.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); return await Task.FromResult(0);
} }
@ -169,7 +146,7 @@ public partial class CoreConfigSingboxService
return await Task.FromResult(finalDns); return await Task.FromResult(finalDns);
} }
private async Task<int> GenDnsRules(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) private async Task<int> GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
{ {
singboxConfig.dns ??= new Dns4Sbox(); singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.rules ??= new List<Rule4Sbox>(); singboxConfig.dns.rules ??= new List<Rule4Sbox>();
@ -180,42 +157,17 @@ public partial class CoreConfigSingboxService
new Rule4Sbox new Rule4Sbox
{ {
server = Global.SingboxRemoteDNSTag, server = Global.SingboxRemoteDNSTag,
strategy = simpleDNSItem.SingboxStrategy4Proxy.NullIfEmpty(), strategy = simpleDNSItem.SingboxStrategy4Proxy.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Proxy,
clash_mode = ERuleMode.Global.ToString() clash_mode = ERuleMode.Global.ToString()
}, },
new Rule4Sbox new Rule4Sbox
{ {
server = Global.SingboxDirectDNSTag, server = Global.SingboxDirectDNSTag,
strategy = simpleDNSItem.SingboxStrategy4Direct.NullIfEmpty(), strategy = simpleDNSItem.SingboxStrategy4Direct.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Direct,
clash_mode = ERuleMode.Direct.ToString() clash_mode = ERuleMode.Direct.ToString()
} }
}); });
var (ech, _) = ParseEchParam(node?.EchConfigList);
if (ech is not null)
{
var echDomain = ech.query_server_name ?? node?.Sni;
singboxConfig.dns.rules.Add(new()
{
query_type = new List<int> { 64, 65 },
server = Global.SingboxEchDNSTag,
domain = echDomain is not null ? new List<string> { echDomain } : null,
});
}
else if (node?.ConfigType.IsGroupType() == true)
{
var queryServerNames = (await ProfileGroupItemManager.GetAllChildEchQuerySni(node.IndexId)).ToList();
if (queryServerNames.Count > 0)
{
singboxConfig.dns.rules.Add(new()
{
query_type = new List<int> { 64, 65 },
server = Global.SingboxEchDNSTag,
domain = queryServerNames,
});
}
}
if (simpleDNSItem.BlockBindingQuery == true) if (simpleDNSItem.BlockBindingQuery == true)
{ {
singboxConfig.dns.rules.Add(new() singboxConfig.dns.rules.Add(new()

View file

@ -10,7 +10,7 @@ public partial class CoreConfigSingboxService
singboxConfig.inbounds = []; singboxConfig.inbounds = [];
if (!_config.TunModeItem.EnableTun if (!_config.TunModeItem.EnableTun
|| (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && AppManager.Instance.RunningCoreType == ECoreType.sing_box)) || (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box))
{ {
var inbound = new Inbound4Sbox() var inbound = new Inbound4Sbox()
{ {

View file

@ -334,11 +334,6 @@ public partial class CoreConfigSingboxService
}; };
tls.insecure = false; tls.insecure = false;
} }
var (ech, _) = ParseEchParam(node.EchConfigList);
if (ech is not null)
{
tls.ech = ech;
}
outbound.tls = tls; outbound.tls = tls;
} }
catch (Exception ex) catch (Exception ex)
@ -359,7 +354,7 @@ public partial class CoreConfigSingboxService
case nameof(ETransport.h2): case nameof(ETransport.h2):
transport.type = nameof(ETransport.http); transport.type = nameof(ETransport.http);
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost); transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
transport.path = node.Path.NullIfEmpty(); transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
break; break;
case nameof(ETransport.tcp): //http case nameof(ETransport.tcp): //http
@ -367,7 +362,7 @@ public partial class CoreConfigSingboxService
{ {
transport.type = nameof(ETransport.http); transport.type = nameof(ETransport.http);
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost); transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
transport.path = node.Path.NullIfEmpty(); transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
} }
break; break;
@ -401,7 +396,7 @@ public partial class CoreConfigSingboxService
} }
} }
transport.path = wsPath.NullIfEmpty(); transport.path = wsPath.IsNullOrEmpty() ? null : wsPath;
if (node.RequestHost.IsNotEmpty()) if (node.RequestHost.IsNotEmpty())
{ {
transport.headers = new() transport.headers = new()
@ -413,8 +408,8 @@ public partial class CoreConfigSingboxService
case nameof(ETransport.httpupgrade): case nameof(ETransport.httpupgrade):
transport.type = nameof(ETransport.httpupgrade); transport.type = nameof(ETransport.httpupgrade);
transport.path = node.Path.NullIfEmpty(); transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
transport.host = node.RequestHost.NullIfEmpty(); transport.host = node.RequestHost.IsNullOrEmpty() ? null : node.RequestHost;
break; break;
@ -909,31 +904,4 @@ public partial class CoreConfigSingboxService
} }
return await Task.FromResult(0); return await Task.FromResult(0);
} }
private (Ech4Sbox? ech, Server4Sbox? dnsServer) ParseEchParam(string? echConfig)
{
if (echConfig.IsNullOrEmpty())
{
return (null, null);
}
if (!echConfig.Contains("://"))
{
return (new Ech4Sbox()
{
enabled = true,
config = [$"-----BEGIN ECH CONFIGS-----\n" +
$"{echConfig}\n" +
$"-----END ECH CONFIGS-----"],
}, null);
}
var idx = echConfig.IndexOf('+');
// NOTE: query_server_name, since sing-box 1.13.0
//var queryServerName = idx > 0 ? echConfig[..idx] : null;
var echDnsServer = idx > 0 ? echConfig[(idx + 1)..] : echConfig;
return (new Ech4Sbox()
{
enabled = true,
query_server_name = null,
}, ParseDnsAddress(echDnsServer));
}
} }

View file

@ -113,7 +113,7 @@ public partial class CoreConfigSingboxService
clash_mode = ERuleMode.Global.ToString() clash_mode = ERuleMode.Global.ToString()
}); });
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty(); var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox;
var defaultRouting = await ConfigHandler.GetDefaultRouting(_config); var defaultRouting = await ConfigHandler.GetDefaultRouting(_config);
if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty()) if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty())
{ {

View file

@ -197,7 +197,7 @@ public partial class CoreConfigV2rayService
if (item.OutboundTag == Global.DirectTag) if (item.OutboundTag == Global.DirectTag)
{ {
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:")) if (normalizedDomain.StartsWith("geosite:"))
{ {
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain); (regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
} }
@ -208,7 +208,7 @@ public partial class CoreConfigV2rayService
} }
else if (item.OutboundTag != Global.BlockTag) else if (item.OutboundTag != Global.BlockTag)
{ {
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:")) if (normalizedDomain.StartsWith("geosite:"))
{ {
proxyGeositeList.Add(normalizedDomain); proxyGeositeList.Add(normalizedDomain);
} }

View file

@ -272,9 +272,7 @@ public partial class CoreConfigV2rayService
{ {
allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure), allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
alpn = node.GetAlpn(), alpn = node.GetAlpn(),
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint, fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
echConfigList = node.EchConfigList.NullIfEmpty(),
echForceQuery = node.EchForceQuery.NullIfEmpty()
}; };
if (sni.IsNotEmpty()) if (sni.IsNotEmpty())
{ {
@ -342,7 +340,7 @@ public partial class CoreConfigV2rayService
kcpSettings.header = new Header4Ray kcpSettings.header = new Header4Ray
{ {
type = node.HeaderType, type = node.HeaderType,
domain = host.NullIfEmpty() domain = host.IsNullOrEmpty() ? null : host
}; };
if (path.IsNotEmpty()) if (path.IsNotEmpty())
{ {
@ -452,7 +450,7 @@ public partial class CoreConfigV2rayService
case nameof(ETransport.grpc): case nameof(ETransport.grpc):
GrpcSettings4Ray grpcSettings = new() GrpcSettings4Ray grpcSettings = new()
{ {
authority = host.NullIfEmpty(), authority = host.IsNullOrEmpty() ? null : host,
serviceName = path, serviceName = path,
multiMode = node.HeaderType == Global.GrpcMultiMode, multiMode = node.HeaderType == Global.GrpcMultiMode,
idle_timeout = _config.GrpcItem.IdleTimeout, idle_timeout = _config.GrpcItem.IdleTimeout,
@ -566,7 +564,7 @@ public partial class CoreConfigV2rayService
var fragmentOutbound = new Outbounds4Ray var fragmentOutbound = new Outbounds4Ray
{ {
protocol = "freedom", protocol = "freedom",
tag = $"frag-{Global.ProxyTag}", tag = $"{Global.ProxyTag}3",
settings = new() settings = new()
{ {
fragment = new() fragment = new()

View file

@ -198,7 +198,6 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{ {
if (!it.AllowTest) if (!it.AllowTest)
{ {
await UpdateFunc(it.IndexId, ResUI.SpeedtestingSkip);
continue; continue;
} }

View file

@ -61,7 +61,7 @@ public class StatisticsSingboxService
await Task.Delay(1000); await Task.Delay(1000);
try try
{ {
if (!AppManager.Instance.IsRunningCore(ECoreType.sing_box)) if (!_config.IsRunningCore(ECoreType.sing_box))
{ {
continue; continue;
} }

View file

@ -30,7 +30,7 @@ public class StatisticsXrayService
await Task.Delay(1000); await Task.Delay(1000);
try try
{ {
if (AppManager.Instance.RunningCoreType != ECoreType.Xray) if (_config.RunningCoreType != ECoreType.Xray)
{ {
continue; continue;
} }

View file

@ -128,7 +128,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
{ {
await Task.Delay(1000 * 5); await Task.Delay(1000 * 5);
numOfExecuted++; numOfExecuted++;
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box))) if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
{ {
continue; continue;
} }

View file

@ -437,7 +437,7 @@ public class ClashProxiesViewModel : MyReactiveObject
{ {
await Task.Delay(1000 * 60); await Task.Delay(1000 * 60);
numOfExecuted++; numOfExecuted++;
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box))) if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
{ {
continue; continue;
} }

View file

@ -253,7 +253,7 @@ public class MainWindowViewModel : MyReactiveObject
private async Task Init() private async Task Init()
{ {
AppManager.Instance.ShowInTaskbar = true; _config.UiItem.ShowInTaskbar = true;
//await ConfigHandler.InitBuiltinRouting(_config); //await ConfigHandler.InitBuiltinRouting(_config);
await ConfigHandler.InitBuiltinDNS(_config); await ConfigHandler.InitBuiltinDNS(_config);
@ -306,7 +306,7 @@ public class MainWindowViewModel : MyReactiveObject
private async Task UpdateStatisticsHandler(ServerSpeedItem update) private async Task UpdateStatisticsHandler(ServerSpeedItem update)
{ {
if (!AppManager.Instance.ShowInTaskbar) if (!_config.UiItem.ShowInTaskbar)
{ {
return; return;
} }
@ -560,7 +560,7 @@ public class MainWindowViewModel : MyReactiveObject
}); });
AppEvents.TestServerRequested.Publish(); AppEvents.TestServerRequested.Publish();
var showClashUI = AppManager.Instance.IsRunningCore(ECoreType.sing_box); var showClashUI = _config.IsRunningCore(ECoreType.sing_box);
if (showClashUI) if (showClashUI)
{ {
AppEvents.ProxiesReloadRequested.Publish(); AppEvents.ProxiesReloadRequested.Publish();

View file

@ -44,7 +44,7 @@ public class MsgViewModel : MyReactiveObject
EnqueueQueueMsg(msg); EnqueueQueueMsg(msg);
if (!AppManager.Instance.ShowInTaskbar) if (!_config.UiItem.ShowInTaskbar)
{ {
return; return;
} }

View file

@ -57,7 +57,6 @@ public class ProfilesViewModel : MyReactiveObject
public ReactiveCommand<Unit, Unit> MoveUpCmd { get; } public ReactiveCommand<Unit, Unit> MoveUpCmd { get; }
public ReactiveCommand<Unit, Unit> MoveDownCmd { get; } public ReactiveCommand<Unit, Unit> MoveDownCmd { get; }
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; } public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
public ReactiveCommand<SubItem, Unit> MoveToGroupCmd { get; }
//servers ping //servers ping
public ReactiveCommand<Unit, Unit> MixedTestServerCmd { get; } public ReactiveCommand<Unit, Unit> MixedTestServerCmd { get; }
@ -180,10 +179,6 @@ public class ProfilesViewModel : MyReactiveObject
{ {
await MoveServer(EMove.Bottom); await MoveServer(EMove.Bottom);
}, canEditRemove); }, canEditRemove);
MoveToGroupCmd = ReactiveCommand.CreateFromTask<SubItem>(async sub =>
{
SelectedMoveToGroup = sub;
});
//servers ping //servers ping
FastRealPingCmd = ReactiveCommand.CreateFromTask(async () => FastRealPingCmd = ReactiveCommand.CreateFromTask(async () =>

View file

@ -549,7 +549,7 @@ public class StatusBarViewModel : MyReactiveObject
try try
{ {
if (AppManager.Instance.IsRunningCore(ECoreType.sing_box)) if (_config.IsRunningCore(ECoreType.sing_box))
{ {
SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, EInboundProtocol.mixed, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown)); SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, EInboundProtocol.mixed, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown));
SpeedDirectDisplay = string.Empty; SpeedDirectDisplay = string.Empty;

View file

@ -713,7 +713,7 @@
Grid.Row="7" Grid.Row="7"
ColumnDefinitions="300,Auto" ColumnDefinitions="300,Auto"
IsVisible="False" IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
@ -768,41 +768,15 @@
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbEchConfigList}" />
<TextBox
x:Name="txtEchConfigList"
Grid.Row="5"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock <TextBlock
Grid.Row="6" Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbEchForceQuery}" />
<ComboBox
x:Name="cmbEchForceQuery"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="7"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertPinning}" /> Text="{x:Static resx:ResUI.TbCertPinning}" />
<StackPanel <StackPanel
Grid.Row="7" Grid.Row="5"
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Center" VerticalAlignment="Center"
Orientation="Horizontal"> Orientation="Horizontal">

View file

@ -28,7 +28,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
cmbFingerprint2.ItemsSource = Global.Fingerprints; cmbFingerprint2.ItemsSource = Global.Fingerprints;
cmbAllowInsecure.ItemsSource = Global.AllowInsecure; cmbAllowInsecure.ItemsSource = Global.AllowInsecure;
cmbAlpn.ItemsSource = Global.Alpns; cmbAlpn.ItemsSource = Global.Alpns;
cmbEchForceQuery.ItemsSource = Global.EchForceQuerys;
var lstStreamSecurity = new List<string>(); var lstStreamSecurity = new List<string>();
lstStreamSecurity.Add(string.Empty); lstStreamSecurity.Add(string.Empty);
@ -188,9 +187,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.EchForceQuery, v => v.cmbEchForceQuery.SelectedValue).DisposeWith(disposables);
//reality //reality
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables);

View file

@ -409,8 +409,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
{ {
var bl = blShow ?? var bl = blShow ??
(Utils.IsLinux() (Utils.IsLinux()
? (!AppManager.Instance.ShowInTaskbar ^ (WindowState == WindowState.Minimized)) ? (!_config.UiItem.ShowInTaskbar ^ (WindowState == WindowState.Minimized))
: !AppManager.Instance.ShowInTaskbar); : !_config.UiItem.ShowInTaskbar);
if (bl) if (bl)
{ {
Show(); Show();
@ -436,7 +436,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
Hide(); Hide();
} }
AppManager.Instance.ShowInTaskbar = bl; _config.UiItem.ShowInTaskbar = bl;
} }
protected override void OnLoaded(object? sender, RoutedEventArgs e) protected override void OnLoaded(object? sender, RoutedEventArgs e)

View file

@ -7,7 +7,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib" xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib" xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
x:Name="Root"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
x:DataType="vms:ProfilesViewModel" x:DataType="vms:ProfilesViewModel"
@ -142,18 +141,19 @@
InputGesture="Ctrl+T" /> InputGesture="Ctrl+T" />
<MenuItem x:Name="menuSortServerResult" Header="{x:Static resx:ResUI.menuSortServerResult}" /> <MenuItem x:Name="menuSortServerResult" Header="{x:Static resx:ResUI.menuSortServerResult}" />
<Separator /> <Separator />
<MenuItem <MenuItem x:Name="menuMoveToGroup" Header="{x:Static resx:ResUI.menuMoveToGroup}">
x:Name="menuMoveToGroup" <MenuItem>
Header="{x:Static resx:ResUI.menuMoveToGroup}" <MenuItem.Header>
ItemsSource="{Binding DataContext.SubItems, ElementName=Root}"> <DockPanel>
<MenuItem.ItemTemplate> <ComboBox
<DataTemplate> x:Name="cmbMoveToGroup"
<MenuItem Width="200"
Command="{Binding DataContext.MoveToGroupCmd, ElementName=Root}" DisplayMemberBinding="{Binding Remarks}"
CommandParameter="{Binding}" ItemsSource="{Binding SubItems}"
Header="{Binding Remarks}" /> ToolTip.Tip="{x:Static resx:ResUI.menuSubscription}" />
</DataTemplate> </DockPanel>
</MenuItem.ItemTemplate> </MenuItem.Header>
</MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="{x:Static resx:ResUI.menuMoveTo}"> <MenuItem Header="{x:Static resx:ResUI.menuMoveTo}">
<MenuItem <MenuItem

View file

@ -69,7 +69,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
//servers move //servers move
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); //this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.SelectedMoveToGroup, v => v.cmbMoveToGroup.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedMoveToGroup, v => v.cmbMoveToGroup.SelectedItem).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveTopCmd, v => v.menuMoveTop).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.MoveTopCmd, v => v.menuMoveTop).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveUpCmd, v => v.menuMoveUp).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.MoveUpCmd, v => v.menuMoveUp).DisposeWith(disposables);

View file

@ -1,7 +1,6 @@
using MaterialDesignColors; using MaterialDesignColors;
using MaterialDesignColors.ColorManipulation; using MaterialDesignColors.ColorManipulation;
using MaterialDesignThemes.Wpf; using MaterialDesignThemes.Wpf;
using Microsoft.Win32;
namespace v2rayN.ViewModels; namespace v2rayN.ViewModels;
@ -25,7 +24,7 @@ public class ThemeSettingViewModel : MyReactiveObject
{ {
_config = AppManager.Instance.Config; _config = AppManager.Instance.Config;
RegisterSystemColorSet(_config, ModifyTheme); RegisterSystemColorSet(_config, Application.Current.MainWindow, ModifyTheme);
BindingUI(); BindingUI();
RestoreUI(); RestoreUI();
@ -159,15 +158,25 @@ public class ThemeSettingViewModel : MyReactiveObject
_paletteHelper.SetTheme(theme); _paletteHelper.SetTheme(theme);
} }
public static void RegisterSystemColorSet(Config config, Action updateFunc) public void RegisterSystemColorSet(Config config, Window window, Action updateFunc)
{ {
SystemEvents.UserPreferenceChanged += (s, e) => var helper = new WindowInteropHelper(window);
var hwndSource = HwndSource.FromHwnd(helper.EnsureHandle());
hwndSource.AddHook((IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) =>
{ {
if ((e.Category == UserPreferenceCategory.Color || e.Category == UserPreferenceCategory.General) if (config.UiItem.CurrentTheme == nameof(ETheme.FollowSystem))
&& config.UiItem.CurrentTheme == nameof(ETheme.FollowSystem)) {
const int WM_SETTINGCHANGE = 0x001A;
if (msg == WM_SETTINGCHANGE)
{
if (wParam == IntPtr.Zero && Marshal.PtrToStringUni(lParam) == "ImmersiveColorSet")
{ {
updateFunc?.Invoke(); updateFunc?.Invoke();
} }
}; }
}
return IntPtr.Zero;
});
} }
} }

View file

@ -929,8 +929,6 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="300" /> <ColumnDefinition Width="300" />
@ -1005,40 +1003,9 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbEchConfigList}" />
<TextBox
x:Name="txtEchConfigList"
Grid.Row="5"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbEchForceQuery}" />
<ComboBox
x:Name="cmbEchForceQuery"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbCertPinning}" /> Text="{x:Static resx:ResUI.TbCertPinning}" />
<StackPanel <StackPanel
Grid.Row="7" Grid.Row="5"
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Center" VerticalAlignment="Center"
Orientation="Horizontal"> Orientation="Horizontal">

View file

@ -23,7 +23,6 @@ public partial class AddServerWindow
cmbFingerprint2.ItemsSource = Global.Fingerprints; cmbFingerprint2.ItemsSource = Global.Fingerprints;
cmbAllowInsecure.ItemsSource = Global.AllowInsecure; cmbAllowInsecure.ItemsSource = Global.AllowInsecure;
cmbAlpn.ItemsSource = Global.Alpns; cmbAlpn.ItemsSource = Global.Alpns;
cmbEchForceQuery.ItemsSource = Global.EchForceQuerys;
var lstStreamSecurity = new List<string>(); var lstStreamSecurity = new List<string>();
lstStreamSecurity.Add(string.Empty); lstStreamSecurity.Add(string.Empty);
@ -183,10 +182,6 @@ public partial class AddServerWindow
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.EchForceQuery, v => v.cmbEchForceQuery.Text).DisposeWith(disposables);
//reality //reality
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.Text).DisposeWith(disposables);

View file

@ -376,7 +376,7 @@ public partial class MainWindow
public void ShowHideWindow(bool? blShow) public void ShowHideWindow(bool? blShow)
{ {
var bl = blShow ?? !AppManager.Instance.ShowInTaskbar; var bl = blShow ?? !_config.UiItem.ShowInTaskbar;
if (bl) if (bl)
{ {
this?.Show(); this?.Show();
@ -391,7 +391,7 @@ public partial class MainWindow
{ {
this?.Hide(); this?.Hide();
} }
AppManager.Instance.ShowInTaskbar = bl; _config.UiItem.ShowInTaskbar = bl;
} }
protected override void OnLoaded(object? sender, RoutedEventArgs e) protected override void OnLoaded(object? sender, RoutedEventArgs e)