Compare commits

..

10 Commits

Author SHA1 Message Date
2dust
6de5a5215d Refactor code
Renamed FileManager to FileUtils and updated all references accordingly. Moved SemanticVersion to the Models namespace. Replaced WindowsJob with WindowsJobService, relocating and updating the implementation. Updated usages in CoreManager and related classes to use the new service and utility names. These changes improve code organization and naming consistency.
2025-11-03 20:01:36 +08:00
2dust
8d86aa2b72 Refactor ping and HTTP helpers, update usages
Moved GetRealPingTime from HttpClientHelper to ConnectionHandler and refactored related methods for clarity. Removed unused and redundant HTTP helper methods. Updated DownloadService and SpeedtestService to use the new method signatures. Simplified UpdateService constructor using primary constructor syntax.
2025-11-03 19:41:02 +08:00
DHR60
1aee3950f4 Fix (#8244) 2025-11-03 19:18:18 +08:00
2dust
091b79f7cf Refactor UpdateService to use instance config and callbacks 2025-11-02 21:04:23 +08:00
DHR60
ed2c77062e Disallow insecure when pinned cert (#8239) 2025-11-02 19:01:58 +08:00
JieXu
8b1105c7e2 Update ResUI.fr.resx (#8238) 2025-11-02 18:58:50 +08:00
2dust
11c203ad19 Update UI and localization for policy group 2025-11-02 16:24:57 +08:00
2dust
ab6a6b879e Code clean 2025-11-02 15:25:41 +08:00
DHR60
b218f0b501 Cert Pinning (#8234)
* Cert Pinning

* Cert Chain Pinning

* Add Trusted Ca Pinning

* Tip

* Perf UI
2025-11-02 15:17:47 +08:00
2dust
7b5686cd8f In the policy group, automatically add filtered configurations from the subscription group.
https://github.com/2dust/v2rayN/issues/8214
2025-11-01 21:13:39 +08:00
80 changed files with 1718 additions and 584 deletions

View File

@@ -8,7 +8,7 @@
<TargetFramework>net8.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<NoWarn>CA1031;CS1591;NU1507;CA1416;IDE0058</NoWarn>
<NoWarn>CA1031;CS1591;NU1507;CA1416;IDE0058;IDE0053;IDE0200</NoWarn>
<Nullable>annotations</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Authors>2dust</Authors>

View File

@@ -3,7 +3,7 @@ using System.IO.Compression;
namespace ServiceLib.Common;
public static class FileManager
public static class FileUtils
{
private static readonly string _tag = "FileManager";

View File

@@ -9,7 +9,7 @@ public class Utils
{
private static readonly string _tag = "Utils";
#region
#region Conversion Functions
/// <summary>
/// Convert to comma-separated string
@@ -306,7 +306,10 @@ public class Utils
public static bool IsBase64String(string? plainText)
{
if (plainText.IsNullOrEmpty())
{
return false;
}
var buffer = new Span<byte>(new byte[plainText.Length]);
return Convert.TryFromBase64String(plainText, buffer, out var _);
}
@@ -459,9 +462,9 @@ public class Utils
return (domain, port);
}
#endregion
#endregion Conversion Functions
#region
#region Data Checks
/// <summary>
/// Determine if the input is a number
@@ -520,40 +523,62 @@ public class Utils
{
// Loopback address check (127.0.0.1 for IPv4, ::1 for IPv6)
if (IPAddress.IsLoopback(address))
{
return true;
}
var ipBytes = address.GetAddressBytes();
if (address.AddressFamily == AddressFamily.InterNetwork)
{
// IPv4 private address check
if (ipBytes[0] == 10)
{
return true;
}
if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31)
{
return true;
}
if (ipBytes[0] == 192 && ipBytes[1] == 168)
{
return true;
}
}
else if (address.AddressFamily == AddressFamily.InterNetworkV6)
{
// IPv6 private address check
// Link-local address fe80::/10
if (ipBytes[0] == 0xfe && (ipBytes[1] & 0xc0) == 0x80)
{
return true;
}
// Unique local address fc00::/7 (typically fd00::/8)
if ((ipBytes[0] & 0xfe) == 0xfc)
{
return true;
}
// Private portion in IPv4-mapped addresses ::ffff:0:0/96
if (address.IsIPv4MappedToIPv6)
{
var ipv4Bytes = ipBytes.Skip(12).ToArray();
if (ipv4Bytes[0] == 10)
{
return true;
}
if (ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31)
{
return true;
}
if (ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168)
{
return true;
}
}
}
}
@@ -561,9 +586,9 @@ public class Utils
return false;
}
#endregion
#endregion Data Checks
#region
#region Speed Test
private static bool PortInUse(int port)
{
@@ -616,9 +641,9 @@ public class Utils
return 59090;
}
#endregion
#endregion Speed Test
#region
#region Miscellaneous
public static bool UpgradeAppExists(out string upgradeFileName)
{
@@ -708,10 +733,16 @@ public class Utils
foreach (var host in hostsList)
{
if (host.StartsWith("#"))
{
continue;
}
var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (hostItem.Length < 2)
{
continue;
}
systemHosts.Add(hostItem[1], hostItem[0]);
}
}
@@ -762,7 +793,7 @@ public class Utils
return null;
}
#endregion
#endregion Miscellaneous
#region TempPath

View File

@@ -1,177 +0,0 @@
namespace ServiceLib.Common;
/*
* See:
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
*/
public sealed class WindowsJob : IDisposable
{
private IntPtr handle = IntPtr.Zero;
public WindowsJob()
{
handle = CreateJobObject(IntPtr.Zero, null);
var extendedInfoPtr = IntPtr.Zero;
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
{
LimitFlags = 0x2000
};
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
BasicLimitInformation = info
};
try
{
var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr,
(uint)length))
{
throw new Exception(string.Format("Unable to set information. Error: {0}",
Marshal.GetLastWin32Error()));
}
}
finally
{
if (extendedInfoPtr != IntPtr.Zero)
{
Marshal.FreeHGlobal(extendedInfoPtr);
}
}
}
public bool AddProcess(IntPtr processHandle)
{
var succ = AssignProcessToJobObject(handle, processHandle);
if (!succ)
{
Logging.SaveLog("Failed to call AssignProcessToJobObject! GetLastError=" + Marshal.GetLastWin32Error());
}
return succ;
}
public bool AddProcess(int processId)
{
return AddProcess(Process.GetProcessById(processId).Handle);
}
#region IDisposable
private bool disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposed)
{
return;
}
disposed = true;
if (disposing)
{
// no managed objects to free
}
if (handle != IntPtr.Zero)
{
CloseHandle(handle);
handle = IntPtr.Zero;
}
}
~WindowsJob()
{
Dispose(false);
}
#endregion IDisposable
#region Interop
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr CreateJobObject(IntPtr a, string? lpName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
#endregion Interop
}
#region Helper classes
[StructLayout(LayoutKind.Sequential)]
internal struct IO_COUNTERS
{
public ulong ReadOperationCount;
public ulong WriteOperationCount;
public ulong OtherOperationCount;
public ulong ReadTransferCount;
public ulong WriteTransferCount;
public ulong OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public long PerProcessUserTimeLimit;
public long PerJobUserTimeLimit;
public uint LimitFlags;
public UIntPtr MinimumWorkingSetSize;
public UIntPtr MaximumWorkingSetSize;
public uint ActiveProcessLimit;
public UIntPtr Affinity;
public uint PriorityClass;
public uint SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public uint nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UIntPtr ProcessMemoryLimit;
public UIntPtr JobMemoryLimit;
public UIntPtr PeakProcessMemoryUsed;
public UIntPtr PeakJobMemoryUsed;
}
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
#endregion Helper classes

View File

@@ -252,6 +252,7 @@ public static class ConfigHandler
item.Mldsa65Verify = profileItem.Mldsa65Verify;
item.Extra = profileItem.Extra;
item.MuxEnabled = profileItem.MuxEnabled;
item.Cert = profileItem.Cert;
}
var ret = item.ConfigType switch
@@ -1650,7 +1651,9 @@ public static class ConfigHandler
var uri = Utils.TryUri(url);
if (uri == null)
{
return -1;
}
//Do not allow http protocol
if (url.StartsWith(Global.HttpProtocol) && !Utils.IsPrivateNetwork(uri.IdnHost))
{
@@ -2017,11 +2020,15 @@ public static class ConfigHandler
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(config.ConstItem.RouteRulesTemplateSourceUrl, true, "");
if (templateContent.IsNullOrEmpty())
{
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
}
var template = JsonUtils.Deserialize<RoutingTemplate>(templateContent);
if (template == null)
{
return await InitBuiltinRouting(config, blImportAdvancedRules); // fallback
}
var items = await AppManager.Instance.RoutingItems();
var maxSort = items.Count;
@@ -2034,14 +2041,18 @@ public static class ConfigHandler
var item = template.RoutingItems[i];
if (item.Url.IsNullOrEmpty() && item.RuleSet.IsNullOrEmpty())
{
continue;
}
var ruleSetsString = !item.RuleSet.IsNullOrEmpty()
? item.RuleSet
: await downloadHandle.TryDownloadString(item.Url, true, "");
if (ruleSetsString.IsNullOrEmpty())
{
continue;
}
item.Remarks = $"{template.Version}-{item.Remarks}";
item.Enabled = true;
@@ -2237,17 +2248,25 @@ public static class ConfigHandler
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
if (templateContent.IsNullOrEmpty())
{
return currentItem;
}
var template = JsonUtils.Deserialize<DNSItem>(templateContent);
if (template == null)
{
return currentItem;
}
if (!template.NormalDNS.IsNullOrEmpty())
{
template.NormalDNS = await downloadHandle.TryDownloadString(template.NormalDNS, true, "");
}
if (!template.TunDNS.IsNullOrEmpty())
{
template.TunDNS = await downloadHandle.TryDownloadString(template.TunDNS, true, "");
}
template.Id = currentItem.Id;
template.Enabled = currentItem.Enabled;
@@ -2281,10 +2300,16 @@ public static class ConfigHandler
var downloadHandle = new DownloadService();
var templateContent = await downloadHandle.TryDownloadString(url, true, "");
if (templateContent.IsNullOrEmpty())
{
return null;
}
var template = JsonUtils.Deserialize<SimpleDNSItem>(templateContent);
if (template == null)
{
return null;
}
return template;
}

View File

@@ -6,7 +6,7 @@ public static class ConnectionHandler
public static async Task<string> RunAvailabilityCheck()
{
var time = await GetRealPingTime();
var time = await GetRealPingTimeInfo();
var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None;
return string.Format(ResUI.TestMeOutput, time, ip);
@@ -39,7 +39,7 @@ public static class ConnectionHandler
return $"({country ?? "unknown"}) {ip}";
}
private static async Task<int> GetRealPingTime()
private static async Task<int> GetRealPingTimeInfo()
{
var responseTime = -1;
try
@@ -50,7 +50,7 @@ public static class ConnectionHandler
for (var i = 0; i < 2; i++)
{
responseTime = await HttpClientHelper.Instance.GetRealPingTime(url, webProxy, 10);
responseTime = await GetRealPingTime(url, webProxy, 10);
if (responseTime > 0)
{
break;
@@ -65,4 +65,34 @@ public static class ConnectionHandler
}
return responseTime;
}
public static async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
{
var responseTime = -1;
try
{
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
using var client = new HttpClient(new SocketsHttpHandler()
{
Proxy = webProxy,
UseProxy = webProxy != null
});
List<int> oneTime = new();
for (var i = 0; i < 2; i++)
{
var timer = Stopwatch.StartNew();
await client.GetAsync(url, cts.Token).ConfigureAwait(false);
timer.Stop();
oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
await Task.Delay(100);
}
responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
}
catch
{
}
return responseTime;
}
}

View File

@@ -12,7 +12,9 @@ public class Hysteria2Fmt : BaseFmt
var url = Utils.TryUri(str);
if (url == null)
{
return null;
}
item.Address = url.IdnHost;
item.Port = url.Port;
@@ -32,7 +34,10 @@ public class Hysteria2Fmt : BaseFmt
public static string? ToUri(ProfileItem? item)
{
if (item == null)
{
return null;
}
var url = string.Empty;
var remark = string.Empty;

View File

@@ -18,7 +18,7 @@ public static class ProxySettingLinux
private static async Task ExecCmd(List<string> args)
{
var fileName = await FileManager.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName), false);
var fileName = await FileUtils.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName), false);
await Utils.GetCliWrapOutput(fileName, args);
}

View File

@@ -23,7 +23,7 @@ public static class ProxySettingOSX
private static async Task ExecCmd(List<string> args)
{
var fileName = await FileManager.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName), false);
var fileName = await FileUtils.CreateLinuxShellFile(_proxySetFileName, EmbedUtils.GetEmbedText(Global.ProxySetOSXShellFileName), false);
await Utils.GetCliWrapOutput(fileName, args);
}

View File

@@ -48,15 +48,7 @@ public class HttpClientHelper
}
return await httpClient.GetStringAsync(url);
}
public async Task<string?> GetAsync(HttpClient client, string url, CancellationToken token = default)
{
if (url.IsNullOrEmpty())
{
return null;
}
return await client.GetStringAsync(url, token);
}
public async Task PutAsync(string url, Dictionary<string, string> headers)
{
@@ -81,155 +73,5 @@ public class HttpClientHelper
await httpClient.DeleteAsync(url);
}
public static async Task DownloadFileAsync(HttpClient client, string url, string fileName, IProgress<double>? progress, CancellationToken token = default)
{
ArgumentNullException.ThrowIfNull(url);
ArgumentNullException.ThrowIfNull(fileName);
if (File.Exists(fileName))
{
File.Delete(fileName);
}
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
if (!response.IsSuccessStatusCode)
{
throw new Exception(response.StatusCode.ToString());
}
var total = response.Content.Headers.ContentLength ?? -1L;
var canReportProgress = total != -1 && progress != null;
await using var stream = await response.Content.ReadAsStreamAsync(token);
await using var file = File.Create(fileName);
var totalRead = 0L;
var buffer = new byte[1024 * 1024];
var progressPercentage = 0;
while (true)
{
token.ThrowIfCancellationRequested();
var read = await stream.ReadAsync(buffer, token);
totalRead += read;
if (read == 0)
{
break;
}
await file.WriteAsync(buffer.AsMemory(0, read), token);
if (canReportProgress)
{
var percent = (int)(100.0 * totalRead / total);
//if (progressPercentage != percent && percent % 10 == 0)
{
progressPercentage = percent;
progress?.Report(percent);
}
}
}
if (canReportProgress)
{
progress?.Report(101);
}
}
public async Task DownloadDataAsync4Speed(HttpClient client, string url, IProgress<string> progress, CancellationToken token = default)
{
if (url.IsNullOrEmpty())
{
throw new ArgumentNullException(nameof(url));
}
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
if (!response.IsSuccessStatusCode)
{
throw new Exception(response.StatusCode.ToString());
}
//var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L;
//var canReportProgress = total != -1 && progress != null;
await using var stream = await response.Content.ReadAsStreamAsync(token);
var totalRead = 0L;
var buffer = new byte[1024 * 64];
var isMoreToRead = true;
var progressSpeed = string.Empty;
var totalDatetime = DateTime.Now;
var totalSecond = 0;
do
{
if (token.IsCancellationRequested)
{
if (totalRead > 0)
{
return;
}
else
{
token.ThrowIfCancellationRequested();
}
}
var read = await stream.ReadAsync(buffer, token);
if (read == 0)
{
isMoreToRead = false;
}
else
{
var data = new byte[read];
buffer.ToList().CopyTo(0, data, 0, read);
totalRead += read;
var ts = DateTime.Now - totalDatetime;
if (progress != null && ts.Seconds > totalSecond)
{
totalSecond = ts.Seconds;
var speed = (totalRead * 1d / ts.TotalMilliseconds / 1000).ToString("#0.0");
if (progressSpeed != speed)
{
progressSpeed = speed;
progress.Report(speed);
}
}
}
} while (isMoreToRead);
}
public async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
{
var responseTime = -1;
try
{
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(downloadTimeout));
using var client = new HttpClient(new SocketsHttpHandler()
{
Proxy = webProxy,
UseProxy = webProxy != null
});
List<int> oneTime = new();
for (var i = 0; i < 2; i++)
{
var timer = Stopwatch.StartNew();
await client.GetAsync(url, cts.Token).ConfigureAwait(false);
timer.Stop();
oneTime.Add((int)timer.Elapsed.TotalMilliseconds);
await Task.Delay(100);
}
responseTime = oneTime.Where(x => x > 0).OrderBy(x => x).FirstOrDefault();
}
catch //(Exception ex)
{
//Utile.SaveLog(ex.Message, ex);
}
return responseTime;
}
}

View File

@@ -81,21 +81,36 @@ public class ActionPrecheckManager(Config config)
{
case EConfigType.VMess:
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
break;
case EConfigType.VLESS:
if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (!Global.Flows.Contains(item.Flow))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
}
break;
case EConfigType.Shadowsocks:
if (item.Id.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Security"));
}
break;
}
@@ -115,7 +130,7 @@ public class ActionPrecheckManager(Config config)
if (item.ConfigType.IsGroupType())
{
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.ChildItems.IsNullOrEmpty())
if (group is null || group.NotHasChild())
{
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
return errors;
@@ -128,7 +143,11 @@ public class ActionPrecheckManager(Config config)
return errors;
}
foreach (var child in Utils.String2List(group.ChildItems))
var childIds = Utils.String2List(group.ChildItems) ?? [];
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
childIds.AddRange(subItems.Select(p => p.IndexId));
foreach (var child in childIds)
{
var childErrors = new List<string>();
if (child.IsNullOrEmpty())

View File

@@ -0,0 +1,323 @@
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
namespace ServiceLib.Manager;
/// <summary>
/// Manager for certificate operations with CA pinning to prevent MITM attacks
/// </summary>
public class CertPemManager
{
private static readonly string _tag = "CertPemManager";
private static readonly Lazy<CertPemManager> _instance = new(() => new());
public static CertPemManager Instance => _instance.Value;
/// <summary>
/// Trusted CA certificate thumbprints (SHA256) to prevent MITM attacks
/// </summary>
private static readonly HashSet<string> TrustedCaThumbprints = new(StringComparer.OrdinalIgnoreCase)
{
"EBD41040E4BB3EC742C9E381D31EF2A41A48B6685C96E7CEF3C1DF6CD4331C99", // GlobalSign Root CA
"6DC47172E01CBCB0BF62580D895FE2B8AC9AD4F873801E0C10B9C837D21EB177", // Entrust.net Premium 2048 Secure Server CA
"73C176434F1BC6D5ADF45B0E76E727287C8DE57616C1E6E6141A2B2CBC7D8E4C", // Entrust Root Certification Authority
"D8E0FEBC1DB2E38D00940F37D27D41344D993E734B99D5656D9778D4D8143624", // Certum Root CA
"D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4", // Comodo AAA Services root
"85A0DD7DD720ADB7FF05F83D542B209DC7FF4528F7D677B18389FEA5E5C49E86", // QuoVadis Root CA 2
"18F1FC7F205DF8ADDDEB7FE007DD57E3AF375A9C4D8D73546BF4F1FED1E18D35", // QuoVadis Root CA 3
"CECDDC905099D8DADFC5B1D209B737CBE2C18CFB2C10C0FF0BCF0D3286FC1AA2", // XRamp Global CA Root
"C3846BF24B9E93CA64274C0EC67C1ECC5E024FFCACD2D74019350E81FE546AE4", // Go Daddy Class 2 CA
"1465FA205397B876FAA6F0A9958E5590E40FCC7FAA4FB7C2C8677521FB5FB658", // Starfield Class 2 CA
"3E9099B5015E8F486C00BCEA9D111EE721FABA355A89BCF1DF69561E3DC6325C", // DigiCert Assured ID Root CA
"4348A0E9444C78CB265E058D5E8944B4D84F9662BD26DB257F8934A443C70161", // DigiCert Global Root CA
"7431E5F4C3C1CE4690774F0B61E05440883BA9A01ED00BA6ABD7806ED3B118CF", // DigiCert High Assurance EV Root CA
"62DD0BE9B9F50A163EA0F8E75C053B1ECA57EA55C8688F647C6881F2C8357B95", // SwissSign Gold CA - G2
"F1C1B50AE5A20DD8030EC9F6BC24823DD367B5255759B4E71B61FCE9F7375D73", // SecureTrust CA
"4200F5043AC8590EBB527D209ED1503029FBCBD41CA1B506EC27F15ADE7DAC69", // Secure Global CA
"0C2CD63DF7806FA399EDE809116B575BF87989F06518F9808C860503178BAF66", // COMODO Certification Authority
"1793927A0614549789ADCE2F8F34F7F0B66D0F3AE3A3B84D21EC15DBBA4FADC7", // COMODO ECC Certification Authority
"41C923866AB4CAD6B7AD578081582E020797A6CBDF4FFF78CE8396B38937D7F5", // OISTE WISeKey Global Root GA CA
"E3B6A2DB2ED7CE48842F7AC53241C7B71D54144BFB40C11F3F1D0B42F5EEA12D", // Certigna
"C0A6F4DC63A24BFDCF54EF2A6A082A0A72DE35803E2FF5FF527AE5D87206DFD5", // ePKI Root Certification Authority
"EAA962C4FA4A6BAFEBE415196D351CCD888D4F53F3FA8AE6D7C466A94E6042BB", // certSIGN ROOT CA
"6C61DAC3A2DEF031506BE036D2A6FE401994FBD13DF9C8D466599274C446EC98", // NetLock Arany (Class Gold) Főtanúsítvány
"3C5F81FEA5FAB82C64BFA2EAECAFCDE8E077FC8620A7CAE537163DF36EDBF378", // Microsec e-Szigno Root CA 2009
"CBB522D7B7F127AD6A0113865BDF1CD4102E7D0759AF635A7CF4720DC963C53B", // GlobalSign Root CA - R3
"2530CC8E98321502BAD96F9B1FBA1B099E2D299E0F4548BB914F363BC0D4531F", // Izenpe.com
"45140B3247EB9CC8C5B4F0D7B53091F73292089E6E5A63E2749DD3ACA9198EDA", // Go Daddy Root Certificate Authority - G2
"2CE1CB0BF9D2F9E102993FBE215152C3B2DD0CABDE1C68E5319B839154DBB7F5", // Starfield Root Certificate Authority - G2
"568D6905A2C88708A4B3025190EDCFEDB1974A606A13C6E5290FCB2AE63EDAB5", // Starfield Services Root Certificate Authority - G2
"0376AB1D54C5F9803CE4B2E201A0EE7EEF7B57B636E8A93C9B8D4860C96F5FA7", // AffirmTrust Commercial
"0A81EC5A929777F145904AF38D5D509F66B5E2C58FCDB531058B0E17F3F0B41B", // AffirmTrust Networking
"70A73F7F376B60074248904534B11482D5BF0E698ECC498DF52577EBF2E93B9A", // AffirmTrust Premium
"BD71FDF6DA97E4CF62D1647ADD2581B07D79ADF8397EB4ECBA9C5E8488821423", // AffirmTrust Premium ECC
"5C58468D55F58E497E743982D2B50010B6D165374ACF83A7D4A32DB768C4408E", // Certum Trusted Network CA
"BFD88FE1101C41AE3E801BF8BE56350EE9BAD1A6B9BD515EDC5C6D5B8711AC44", // TWCA Root Certification Authority
"513B2CECB810D4CDE5DD85391ADFC6C2DD60D87BB736D2B521484AA47A0EBEF6", // Security Communication RootCA2
"55926084EC963A64B96E2ABE01CE0BA86A64FBFEBCC7AAB5AFC155B37FD76066", // Actalis Authentication Root CA
"9A114025197C5BB95D94E63D55CD43790847B646B23CDF11ADA4A00EFF15FB48", // Buypass Class 2 Root CA
"EDF7EBBCA27A2A384D387B7D4010C666E2EDB4843E4C29B4AE1D5B9332E6B24D", // Buypass Class 3 Root CA
"FD73DAD31C644FF1B43BEF0CCDDA96710B9CD9875ECA7E31707AF3E96D522BBD", // T-TeleSec GlobalRoot Class 3
"49E7A442ACF0EA6287050054B52564B650E4F49E42E348D6AA38E039E957B1C1", // D-TRUST Root Class 3 CA 2 2009
"EEC5496B988CE98625B934092EEC2908BED0B0F316C2D4730C84EAF1F3D34881", // D-TRUST Root Class 3 CA 2 EV 2009
"E23D4A036D7B70E9F595B1422079D2B91EDFBB1FB651A0633EAA8A9DC5F80703", // CA Disig Root R2
"9A6EC012E1A7DA9DBE34194D478AD7C0DB1822FB071DF12981496ED104384113", // ACCVRAIZ1
"59769007F7685D0FCD50872F9F95D5755A5B2B457D81F3692B610A98672F0E1B", // TWCA Global Root CA
"DD6936FE21F8F077C123A1A521C12224F72255B73E03A7260693E8A24B0FA389", // TeliaSonera Root CA v1
"91E2F5788D5810EBA7BA58737DE1548A8ECACD014598BC0B143E041B17052552", // T-TeleSec GlobalRoot Class 2
"F356BEA244B7A91EB35D53CA9AD7864ACE018E2D35D5F8F96DDF68A6F41AA474", // Atos TrustedRoot 2011
"8A866FD1B276B57E578E921C65828A2BED58E9F2F288054134B7F1F4BFC9CC74", // QuoVadis Root CA 1 G3
"8FE4FB0AF93A4D0D67DB0BEBB23E37C71BF325DCBCDD240EA04DAF58B47E1840", // QuoVadis Root CA 2 G3
"88EF81DE202EB018452E43F864725CEA5FBD1FC2D9D205730709C5D8B8690F46", // QuoVadis Root CA 3 G3
"7D05EBB682339F8C9451EE094EEBFEFA7953A114EDB2F44949452FAB7D2FC185", // DigiCert Assured ID Root G2
"7E37CB8B4C47090CAB36551BA6F45DB840680FBA166A952DB100717F43053FC2", // DigiCert Assured ID Root G3
"CB3CCBB76031E5E0138F8DD39A23F9DE47FFC35E43C1144CEA27D46A5AB1CB5F", // DigiCert Global Root G2
"31AD6648F8104138C738F39EA4320133393E3A18CC02296EF97C2AC9EF6731D0", // DigiCert Global Root G3
"552F7BDCF1A7AF9E6CE672017F4F12ABF77240C78E761AC203D1D9D20AC89988", // DigiCert Trusted Root G4
"52F0E1C4E58EC629291B60317F074671B85D7EA80D5B07273463534B32B40234", // COMODO RSA Certification Authority
"E793C9B02FD8AA13E21C31228ACCB08119643B749C898964B1746D46C3D4CBD2", // USERTrust RSA Certification Authority
"4FF460D54B9C86DABFBCFC5712E0400D2BED3FBC4D4FBDAA86E06ADCD2A9AD7A", // USERTrust ECC Certification Authority
"179FBC148A3DD00FD24EA13458CC43BFA7F59C8182D783A513F6EBEC100C8924", // GlobalSign ECC Root CA - R5
"3C4FB0B95AB8B30032F432B86F535FE172C185D0FD39865837CF36187FA6F428", // Staat der Nederlanden Root CA - G3
"5D56499BE4D2E08BCFCAD08A3E38723D50503BDE706948E42F55603019E528AE", // IdenTrust Commercial Root CA 1
"30D0895A9A448A262091635522D1F52010B5867ACAE12C78EF958FD4F4389F2F", // IdenTrust Public Sector Root CA 1
"43DF5774B03E7FEF5FE40D931A7BEDF1BB2E6B42738C4E6D3841103D3AA7F339", // Entrust Root Certification Authority - G2
"02ED0EB28C14DA45165C566791700D6451D7FB56F0B2AB1D3B8EB070E56EDFF5", // Entrust Root Certification Authority - EC1
"5CC3D78E4E1D5E45547A04E6873E64F90CF9536D1CCC2EF800F355C4C5FD70FD", // CFCA EV ROOT
"6B9C08E86EB0F767CFAD65CD98B62149E5494A67F5845E7BD1ED019F27B86BD6", // OISTE WISeKey Global Root GB CA
"A1339D33281A0B56E557D3D32B1CE7F9367EB094BD5FA72A7E5004C8DED7CAFE", // SZAFIR ROOT CA2
"B676F2EDDAE8775CD36CB0F63CD1D4603961F49E6265BA013A2F0307B6D0B804", // Certum Trusted Network CA 2
"A040929A02CE53B4ACF4F2FFC6981CE4496F755E6D45FE0B2A692BCD52523F36", // Hellenic Academic and Research Institutions RootCA 2015
"44B545AA8A25E65A73CA15DC27FC36D24C1CB9953A066539B11582DC487B4833", // Hellenic Academic and Research Institutions ECC RootCA 2015
"96BCEC06264976F37460779ACF28C5A7CFE8A3C0AAE11A8FFCEE05C0BDDF08C6", // ISRG Root X1
"EBC5570C29018C4D67B1AA127BAF12F703B4611EBC17B7DAB5573894179B93FA", // AC RAIZ FNMT-RCM
"8ECDE6884F3D87B1125BA31AC3FCB13D7016DE7F57CC904FE1CB97C6AE98196E", // Amazon Root CA 1
"1BA5B2AA8C65401A82960118F80BEC4F62304D83CEC4713A19C39C011EA46DB4", // Amazon Root CA 2
"18CE6CFE7BF14E60B2E347B8DFE868CB31D02EBB3ADA271569F50343B46DB3A4", // Amazon Root CA 3
"E35D28419ED02025CFA69038CD623962458DA5C695FBDEA3C22B0BFB25897092", // Amazon Root CA 4
"A1A86D04121EB87F027C66F53303C28E5739F943FC84B38AD6AF009035DD9457", // D-TRUST Root CA 3 2013
"46EDC3689046D53A453FB3104AB80DCAEC658B2660EA1629DD7E867990648716", // TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1
"BFFF8FD04433487D6A8AA60C1A29767A9FC2BBB05E420F713A13B992891D3893", // GDCA TrustAUTH R5 ROOT
"85666A562EE0BE5CE925C1D8890A6F76A87EC16D4D7D5F29EA7419CF20123B69", // SSL.com Root Certification Authority RSA
"3417BB06CC6007DA1B961C920B8AB4CE3FAD820E4AA30B9ACBC4A74EBDCEBC65", // SSL.com Root Certification Authority ECC
"2E7BF16CC22485A7BBE2AA8696750761B0AE39BE3B2FE9D0CC6D4EF73491425C", // SSL.com EV Root Certification Authority RSA R2
"22A2C1F7BDED704CC1E701B5F408C310880FE956B5DE2A4A44F99C873A25A7C8", // SSL.com EV Root Certification Authority ECC
"2CABEAFE37D06CA22ABA7391C0033D25982952C453647349763A3AB5AD6CCF69", // GlobalSign Root CA - R6
"8560F91C3624DABA9570B5FEA0DBE36FF11A8323BE9486854FB3F34A5571198D", // OISTE WISeKey Global Root GC CA
"9BEA11C976FE014764C1BE56A6F914B5A560317ABD9988393382E5161AA0493C", // UCA Global G2 Root
"D43AF9B35473755C9684FC06D7D8CB70EE5C28E773FB294EB41EE71722924D24", // UCA Extended Validation Root
"D48D3D23EEDB50A459E55197601C27774B9D7B18C94D5A059511A10250B93168", // Certigna Root CA
"40F6AF0346A99AA1CD1D555A4E9CCE62C7F9634603EE406615833DC8C8D00367", // emSign Root CA - G1
"86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B", // emSign ECC Root CA - G3
"125609AA301DA0A249B97A8239CB6A34216F44DCAC9F3954B14292F2E8C8608F", // emSign Root CA - C1
"BC4D809B15189D78DB3E1D8CF4F9726A795DA1643CA5F1358E1DDB0EDC0D7EB3", // emSign ECC Root CA - C3
"5A2FC03F0C83B090BBFA40604B0988446C7636183DF9846E17101A447FB8EFD6", // Hongkong Post Root CA 3
"DB3517D1F6732A2D5AB97C533EC70779EE3270A62FB4AC4238372460E6F01E88", // Entrust Root Certification Authority - G4
"358DF39D764AF9E1B766E9C972DF352EE15CFAC227AF6AD1D70E8E4A6EDCBA02", // Microsoft ECC Root Certificate Authority 2017
"C741F70F4B2A8D88BF2E71C14122EF53EF10EBA0CFA5E64CFA20F418853073E0", // Microsoft RSA Root Certificate Authority 2017
"BEB00B30839B9BC32C32E4447905950641F26421B15ED089198B518AE2EA1B99", // e-Szigno Root CA 2017
"657CFE2FA73FAA38462571F332A2363A46FCE7020951710702CDFBB6EEDA3305", // certSIGN Root CA G2
"97552015F5DDFC3C8788C006944555408894450084F100867086BC1A2BB58DC8", // Trustwave Global Certification Authority
"945BBC825EA554F489D1FD51A73DDF2EA624AC7019A05205225C22A78CCFA8B4", // Trustwave Global ECC P256 Certification Authority
"55903859C8C0C3EBB8759ECE4E2557225FF5758BBD38EBD48276601E1BD58097", // Trustwave Global ECC P384 Certification Authority
"88F438DCF8FFD1FA8F429115FFE5F82AE1E06E0C70C375FAAD717B34A49E7265", // NAVER Global Root Certification Authority
"554153B13D2CF9DDB753BFBE1A4E0AE08D0AA4187058FE60A2B862B2E4B87BCB", // AC RAIZ FNMT-RCM SERVIDORES SEGUROS
"319AF0A7729E6F89269C131EA6A3A16FCD86389FDCAB3C47A4A675C161A3F974", // GlobalSign Secure Mail Root R45
"5CBF6FB81FD417EA4128CD6F8172A3C9402094F74AB2ED3A06B4405D04F30B19", // GlobalSign Secure Mail Root E45
"4FA3126D8D3A11D1C4855A4F807CBAD6CF919D3A5A88B03BEA2C6372D93C40C9", // GlobalSign Root R46
"CBB9C44D84B8043E1050EA31A69F514955D7BFD2E2C6B49301019AD61D9F5058", // GlobalSign Root E46
"9A296A5182D1D451A2E37F439B74DAAFA267523329F90F9A0D2007C334E23C9A", // GLOBALTRUST 2020
"FB8FEC759169B9106B1E511644C618C51304373F6C0643088D8BEFFD1B997599", // ANF Secure Server Root CA
"6B328085625318AA50D173C98D8BDA09D57E27413D114CF787A0F5D06C030CF6", // Certum EC-384 CA
"FE7696573855773E37A95E7AD4D9CC96C30157C15D31765BA9B15704E1AE78FD", // Certum Trusted Root CA
"2E44102AB58CB85419451C8E19D9ACF3662CAFBC614B6A53960A30F7D0E2EB41", // TunTrust Root CA
"D95D0E8EDA79525BF9BEB11B14D2100D3294985F0C62D9FABD9CD999ECCB7B1D", // HARICA TLS RSA Root CA 2021
"3F99CC474ACFCE4DFED58794665E478D1547739F2E780F1BB4CA9B133097D401", // HARICA TLS ECC Root CA 2021
"1BE7ABE30686B16348AFD1C61B6866A0EA7F4821E67D5E8AF937CF8011BC750D", // HARICA Client RSA Root CA 2021
"8DD4B5373CB0DE36769C12339280D82746B3AA6CD426E797A31BABE4279CF00B", // HARICA Client ECC Root CA 2021
"57DE0583EFD2B26E0361DA99DA9DF4648DEF7EE8441C3B728AFA9BCDE0F9B26A", // Autoridad de Certificacion Firmaprofesional CIF A62634068
"30FBBA2C32238E2A98547AF97931E550428B9B3F1C8EEB6633DCFA86C5B27DD3", // vTrus ECC Root CA
"8A71DE6559336F426C26E53880D00D88A18DA4C6A91F0DCB6194E206C5C96387", // vTrus Root CA
"69729B8E15A86EFC177A57AFB7171DFC64ADD28C2FCA8CF1507E34453CCB1470", // ISRG Root X2
"F015CE3CC239BFEF064BE9F1D2C417E1A0264A0A94BE1F0C8D121864EB6949CC", // HiPKI Root CA - G1
"B085D70B964F191A73E4AF0D54AE7A0E07AAFDAF9B71DD0862138AB7325A24A2", // GlobalSign ECC Root CA - R4
"D947432ABDE7B7FA90FC2E6B59101B1280E0E1C7E4E40FA3C6887FFF57A7F4CF", // GTS Root R1
"8D25CD97229DBF70356BDA4EB3CC734031E24CF00FAFCFD32DC76EB5841C7EA8", // GTS Root R2
"34D8A73EE208D9BCDB0D956520934B4E40E69482596E8B6F73C8426B010A6F48", // GTS Root R3
"349DFA4058C5E263123B398AE795573C4E1313C83FE68F93556CD5E8031B3C7D", // GTS Root R4
"242B69742FCB1E5B2ABF98898B94572187544E5B4D9911786573621F6A74B82C", // Telia Root CA v2
"E59AAA816009C22BFF5B25BAD37DF306F049797C1F81D85AB089E657BD8F0044", // D-TRUST BR Root CA 1 2020
"08170D1AA36453901A2F959245E347DB0C8D37ABAABC56B81AA100DC958970DB", // D-TRUST EV Root CA 1 2020
"018E13F0772532CF809BD1B17281867283FC48C6E13BE9C69812854A490C1B05", // DigiCert TLS ECC P384 Root G5
"371A00DC0533B3721A7EEB40E8419E70799D2B0A0F2C1D80693165F7CEC4AD75", // DigiCert TLS RSA4096 Root G5
"E8E8176536A60CC2C4E10187C3BEFCA20EF263497018F566D5BEA0F94D0C111B", // DigiCert SMIME ECC P384 Root G5
"90370D3EFA88BF58C30105BA25104A358460A7FA52DFC2011DF233A0F417912A", // DigiCert SMIME RSA4096 Root G5
"77B82CD8644C4305F7ACC5CB156B45675004033D51C60C6202A8E0C33467D3A0", // Certainly Root R1
"B4585F22E4AC756A4E8612A1361C5D9D031A93FD84FEBB778FA3068B0FC42DC2", // Certainly Root E1
"82BD5D851ACF7F6E1BA7BFCBC53030D0E7BC3C21DF772D858CAB41D199BDF595", // DIGITALSIGN GLOBAL ROOT RSA CA
"261D7114AE5F8FF2D8C7209A9DE4289E6AFC9D717023D85450909199F1857CFE", // DIGITALSIGN GLOBAL ROOT ECDSA CA
"E74FBDA55BD564C473A36B441AA799C8A68E077440E8288B9FA1E50E4BBACA11", // Security Communication ECC RootCA1
"F3896F88FE7C0A882766A7FA6AD2749FB57A7F3E98FB769C1FA7B09C2C44D5AE", // BJCA Global Root CA1
"574DF6931E278039667B720AFDC1600FC27EB66DD3092979FB73856487212882", // BJCA Global Root CA2
"48E1CF9E43B688A51044160F46D773B8277FE45BEAAD0E4DF90D1974382FEA99", // LAWtrust Root CA2 (4096)
"22D9599234D60F1D4BC7C7E96F43FA555B07301FD475175089DAFB8C25E477B3", // Sectigo Public Email Protection Root E46
"D5917A7791EB7CF20A2E57EB98284A67B28A57E89182DA53D546678C9FDE2B4F", // Sectigo Public Email Protection Root R46
"C90F26F0FB1B4018B22227519B5CA2B53E2CA5B3BE5CF18EFE1BEF47380C5383", // Sectigo Public Server Authentication Root E46
"7BB647A62AEEAC88BF257AA522D01FFEA395E0AB45C73F93F65654EC38F25A06", // Sectigo Public Server Authentication Root R46
"8FAF7D2E2CB4709BB8E0B33666BF75A5DD45B5DE480F8EA8D4BFE6BEBC17F2ED", // SSL.com TLS RSA Root CA 2022
"C32FFD9F46F936D16C3673990959434B9AD60AAFBB9E7CF33654F144CC1BA143", // SSL.com TLS ECC Root CA 2022
"AD7DD58D03AEDB22A30B5084394920CE12230C2D8017AD9B81AB04079BDD026B", // SSL.com Client ECC Root CA 2022
"1D4CA4A2AB21D0093659804FC0EB2175A617279B56A2475245C9517AFEB59153", // SSL.com Client RSA Root CA 2022
"E38655F4B0190C84D3B3893D840A687E190A256D98052F159E6D4A39F589A6EB", // Atos TrustedRoot Root CA ECC G2 2020
"78833A783BB2986C254B9370D3C20E5EBA8FA7840CBF63FE17297A0B0119685E", // Atos TrustedRoot Root CA RSA G2 2020
"B2FAE53E14CCD7AB9212064701AE279C1D8988FACB775FA8A008914E663988A8", // Atos TrustedRoot Root CA ECC TLS 2021
"81A9088EA59FB364C548A6F85559099B6F0405EFBF18E5324EC9F457BA00112F", // Atos TrustedRoot Root CA RSA TLS 2021
"E0D3226AEB1163C2E48FF9BE3B50B4C6431BE7BB1EACC5C36B5D5EC509039A08", // TrustAsia Global Root CA G3
"BE4B56CB5056C0136A526DF444508DAA36A0B54F42E4AC38F72AF470E479654C", // TrustAsia Global Root CA G4
"D92C171F5CF890BA428019292927FE22F3207FD2B54449CB6F675AF4922146E2", // D-Trust SBR Root CA 1 2022
"DBA84DD7EF622D485463A90137EA4D574DF8550928F6AFA03B4D8B1141E636CC", // D-Trust SBR Root CA 2 2022
"3AE6DF7E0D637A65A8C81612EC6F9A142F85A16834C10280D88E707028518755", // Telekom Security SMIME ECC Root 2021
"578AF4DED0853F4E5998DB4AEAF9CBEA8D945F60B620A38D1A3C13B2BC7BA8E1", // Telekom Security TLS ECC Root 2020
"78A656344F947E9CC0F734D9053D32F6742086B6B9CD2CAE4FAE1A2E4EFDE048", // Telekom Security SMIME RSA Root 2023
"EFC65CADBB59ADB6EFE84DA22311B35624B71B3B1EA0DA8B6655174EC8978646", // Telekom Security TLS RSA Root 2023
"BEF256DAF26E9C69BDEC1602359798F3CAF71821A03E018257C53C65617F3D4A", // FIRMAPROFESIONAL CA ROOT-A WEB
"3F63BB2814BE174EC8B6439CF08D6D56F0B7C405883A5648A334424D6B3EC558", // TWCA CYBER Root CA
"3A0072D49FFC04E996C59AEB75991D3C340F3615D6FD4DCE90AC0B3D88EAD4F4", // TWCA Global Root CA G2
"3F034BB5704D44B2D08545A02057DE93EBF3905FCE721ACBC730C06DDAEE904E", // SecureSign Root CA12
"4B009C1034494F9AB56BBA3BA1D62731FC4D20D8955ADCEC10A925607261E338", // SecureSign Root CA14
"E778F0F095FE843729CD1A0082179E5314A9C291442805E1FB1D8FB6B8886C3A", // SecureSign Root CA15
"0552E6F83FDF65E8FA9670E666DF28A4E21340B510CBE52566F97C4FB94B2BD1", // D-TRUST BR Root CA 2 2023
"436472C1009A325C54F1A5BBB5468A7BAEECCBE05DE5F099CB70D3FE41E13C16", // TrustAsia SMIME ECC Root CA
"C7796BEB62C101BB143D262A7C96A0C6168183223EF50D699632D86E03B8CC9B", // TrustAsia SMIME RSA Root CA
"C0076B9EF0531FB1A656D67C4EBE97CD5DBAA41EF44598ACC2489878C92D8711", // TrustAsia TLS ECC Root CA
"06C08D7DAFD876971EB1124FE67F847EC0C7A158D3EA53CBE940E2EA9791F4C3", // TrustAsia TLS RSA Root CA
"8E8221B2E7D4007836A1672F0DCC299C33BC07D316F132FA1A206D587150F1CE", // D-TRUST EV Root CA 2 2023
"9A12C392BFE57891A0C545309D4D9FD567E480CB613D6342278B195C79A7931F", // SwissSign RSA SMIME Root CA 2022 - 1
"193144F431E0FDDB740717D4DE926A571133884B4360D30E272913CBE660CE41", // SwissSign RSA TLS Root CA 2022 - 1
"D9A32485A8CCA85539CEF12FFFFF711378A17851D73DA2732AB4302D763BD62B", // OISTE Client Root ECC G1
"D02A0F994A868C66395F2E7A880DF509BD0C29C96DE16015A0FD501EDA4F96A9", // OISTE Client Root RSA G1
"EEC997C0C30F216F7E3B8B307D2BAE42412D753FC8219DAFD1520B2572850F49", // OISTE Server Root ECC G1
"9AE36232A5189FFDDB353DFD26520C015395D22777DAC59DB57B98C089A651E6", // OISTE Server Root RSA G1
};
/// <summary>
/// Get certificate in PEM format from a server with CA pinning validation
/// </summary>
public async Task<string?> GetCertPemAsync(string target, string serverName)
{
try
{
var (domain, _, port, _) = Utils.ParseUrl(target);
using var client = new TcpClient();
await client.ConnectAsync(domain, port > 0 ? port : 443);
using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
await ssl.AuthenticateAsClientAsync(serverName);
var remote = ssl.RemoteCertificate;
if (remote == null)
{
return null;
}
var leaf = new X509Certificate2(remote);
return ExportCertToPem(leaf);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
return null;
}
}
/// <summary>
/// Get certificate chain in PEM format from a server with CA pinning validation
/// </summary>
public async Task<List<string>> GetCertChainPemAsync(string target, string serverName)
{
try
{
var pemList = new List<string>();
var (domain, _, port, _) = Utils.ParseUrl(target);
using var client = new TcpClient();
await client.ConnectAsync(domain, port > 0 ? port : 443);
using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate);
await ssl.AuthenticateAsClientAsync(serverName);
if (ssl.RemoteCertificate is not X509Certificate2 certChain)
{
return pemList;
}
var chain = new X509Chain();
chain.Build(certChain);
foreach (var element in chain.ChainElements)
{
var pem = ExportCertToPem(element.Certificate);
pemList.Add(pem);
}
return pemList;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
return new List<string>();
}
}
/// <summary>
/// Validate server certificate with CA pinning
/// </summary>
private bool ValidateServerCertificate(
object sender,
X509Certificate? certificate,
X509Chain? chain,
SslPolicyErrors sslPolicyErrors)
{
if (certificate == null)
{
return false;
}
// Check certificate name mismatch
if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch))
{
return false;
}
// Build certificate chain
var cert2 = certificate as X509Certificate2 ?? new X509Certificate2(certificate);
var certChain = chain ?? new X509Chain();
certChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
certChain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
certChain.ChainPolicy.VerificationTime = DateTime.Now;
certChain.Build(cert2);
// Find root CA
if (certChain.ChainElements.Count == 0)
{
return false;
}
var rootCert = certChain.ChainElements[certChain.ChainElements.Count - 1].Certificate;
var rootThumbprint = rootCert.GetCertHashString(HashAlgorithmName.SHA256);
return TrustedCaThumbprints.Contains(rootThumbprint);
}
public string ExportCertToPem(X509Certificate2 cert)
{
var der = cert.Export(X509ContentType.Cert);
var b64 = Convert.ToBase64String(der, Base64FormattingOptions.InsertLineBreaks);
return $"-----BEGIN CERTIFICATE-----\n{b64}\n-----END CERTIFICATE-----\n";
}
}

View File

@@ -35,7 +35,7 @@ public class CoreAdminManager
sb.AppendLine("#!/bin/bash");
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
sb.AppendLine($"exec sudo -S -- {cmdLine}");
var shFilePath = await FileManager.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
var shFilePath = await FileUtils.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
var procService = new ProcessService(
fileName: shFilePath,
@@ -68,7 +68,7 @@ public class CoreAdminManager
try
{
var shellFileName = Utils.IsOSX() ? Global.KillAsSudoOSXShellFileName : Global.KillAsSudoLinuxShellFileName;
var shFilePath = await FileManager.CreateLinuxShellFile("kill_as_sudo.sh", EmbedUtils.GetEmbedText(shellFileName), true);
var shFilePath = await FileUtils.CreateLinuxShellFile("kill_as_sudo.sh", EmbedUtils.GetEmbedText(shellFileName), true);
if (shFilePath.Contains(' '))
{
shFilePath = shFilePath.AppendQuotes();

View File

@@ -8,7 +8,7 @@ public class CoreManager
private static readonly Lazy<CoreManager> _instance = new(() => new());
public static CoreManager Instance => _instance.Value;
private Config _config;
private WindowsJob? _processJob;
private WindowsJobService? _processJob;
private ProcessService? _processService;
private ProcessService? _processPreService;
private bool _linuxSudo = false;
@@ -27,7 +27,7 @@ public class CoreManager
var toPath = Utils.GetBinPath("");
if (fromPath != toPath)
{
FileManager.CopyDirectory(fromPath, toPath, true, false);
FileUtils.CopyDirectory(fromPath, toPath, true, false);
}
}

View File

@@ -173,13 +173,19 @@ public class ProfileGroupItemManager
public static bool HasCycle(string? indexId, HashSet<string> visited, HashSet<string> stack)
{
if (indexId.IsNullOrEmpty())
{
return false;
}
if (stack.Contains(indexId))
{
return true;
}
if (visited.Contains(indexId))
{
return false;
}
visited.Add(indexId);
stack.Add(indexId);
@@ -220,11 +226,14 @@ public class ProfileGroupItemManager
public static async Task<(List<ProfileItem> Items, ProfileGroupItem? Group)> GetChildProfileItems(string? indexId)
{
Instance.TryGet(indexId, out var profileGroupItem);
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
if (profileGroupItem == null || profileGroupItem.NotHasChild())
{
return (new List<ProfileItem>(), profileGroupItem);
}
var items = await GetChildProfileItems(profileGroupItem);
var subItems = await GetSubChildProfileItems(profileGroupItem);
items.AddRange(subItems);
return (items, profileGroupItem);
}
@@ -248,20 +257,47 @@ public class ProfileGroupItemManager
return childProfiles;
}
public static async Task<List<ProfileItem>> GetSubChildProfileItems(ProfileGroupItem? group)
{
if (group == null || group.SubChildItems.IsNullOrEmpty())
{
return new();
}
var childProfiles = await AppManager.Instance.ProfileItems(group.SubChildItems);
return childProfiles.Where(p =>
p != null &&
p.IsValid() &&
!p.ConfigType.IsComplexType() &&
(group.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, group.Filter))
)
.ToList();
}
public static async Task<HashSet<string>> GetAllChildDomainAddresses(string indexId)
{
// include grand children
var childAddresses = new HashSet<string>();
if (!Instance.TryGet(indexId, out var groupItem) || groupItem.ChildItems.IsNullOrEmpty())
if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null)
{
return childAddresses;
}
var childIds = Utils.String2List(groupItem.ChildItems);
if (groupItem.SubChildItems.IsNotEmpty())
{
var subItems = await GetSubChildProfileItems(groupItem);
subItems.ForEach(p => childAddresses.Add(p.Address));
}
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())
{

View File

@@ -56,9 +56,9 @@ public class TaskManager
{
//Logging.SaveLog("Execute delete expired files");
FileManager.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1));
FileManager.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1));
FileManager.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
FileUtils.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1));
FileUtils.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddMonths(-1));
FileUtils.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddMonths(-1));
try
{
@@ -111,11 +111,10 @@ public class TaskManager
{
Logging.SaveLog("Execute update geo files");
var updateHandle = new UpdateService();
await updateHandle.UpdateGeoFileAll(_config, async (success, msg) =>
await new UpdateService(_config, async (success, msg) =>
{
await _updateFunc?.Invoke(false, msg);
});
}).UpdateGeoFileAll();
}
}
}

View File

@@ -8,5 +8,14 @@ public class ProfileGroupItem
public string ChildItems { get; set; }
public string? SubChildItems { get; set; }
public string? Filter { get; set; }
public EMultipleLoad MultipleLoad { get; set; } = EMultipleLoad.LeastPing;
public bool NotHasChild()
{
return string.IsNullOrWhiteSpace(ChildItems) && string.IsNullOrWhiteSpace(SubChildItems);
}
}

View File

@@ -69,30 +69,49 @@ public class ProfileItem : ReactiveObject
public bool IsValid()
{
if (IsComplex())
{
return true;
}
if (Address.IsNullOrEmpty() || Port is <= 0 or >= 65536)
{
return false;
}
switch (ConfigType)
{
case EConfigType.VMess:
if (Id.IsNullOrEmpty() || !Utils.IsGuidByParse(Id))
{
return false;
}
break;
case EConfigType.VLESS:
if (Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(Id) && Id.Length > 30))
{
return false;
}
if (!Global.Flows.Contains(Flow))
{
return false;
}
break;
case EConfigType.Shadowsocks:
if (Id.IsNullOrEmpty())
{
return false;
}
if (string.IsNullOrEmpty(Security) || !Global.SsSecuritiesInSingbox.Contains(Security))
{
return false;
}
break;
}
@@ -141,4 +160,5 @@ public class ProfileItem : ReactiveObject
public string Mldsa65Verify { get; set; }
public string Extra { get; set; }
public bool? MuxEnabled { get; set; }
public string Cert { get; set; }
}

View File

@@ -1,4 +1,4 @@
namespace ServiceLib.Common;
namespace ServiceLib.Models;
public class SemanticVersion
{

View File

@@ -181,6 +181,7 @@ public class Tls4Sbox
public bool? fragment { get; set; }
public string? fragment_fallback_delay { get; set; }
public bool? record_fragment { get; set; }
public List<string>? certificate { get; set; }
}
public class Multiplex4Sbox

View File

@@ -354,6 +354,14 @@ public class TlsSettings4Ray
public string? shortId { get; set; }
public string? spiderX { get; set; }
public string? mldsa65Verify { get; set; }
public List<CertificateSettings4Ray>? certificates { get; set; }
public bool? disableSystemRoot { get; set; }
}
public class CertificateSettings4Ray
{
public List<string>? certificate { get; set; }
public string? usage { get; set; }
}
public class TcpSettings4Ray

View File

@@ -19,7 +19,7 @@ namespace ServiceLib.Resx {
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ResUI {
@@ -87,6 +87,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Certificate not set 的本地化字符串。
/// </summary>
public static string CertNotSet {
get {
return ResourceManager.GetString("CertNotSet", resourceCulture);
}
}
/// <summary>
/// 查找类似 Certificate set 的本地化字符串。
/// </summary>
public static string CertSet {
get {
return ResourceManager.GetString("CertSet", resourceCulture);
}
}
/// <summary>
/// 查找类似 Please check the Configuration settings first. 的本地化字符串。
/// </summary>
@@ -1645,7 +1663,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Server List 的本地化字符串。
/// 查找类似 Configuration List 的本地化字符串。
/// </summary>
public static string menuServerList {
get {
@@ -2301,6 +2319,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Please set a valid domain 的本地化字符串。
/// </summary>
public static string ServerNameMustBeValidDomain {
get {
return ResourceManager.GetString("ServerNameMustBeValidDomain", resourceCulture);
}
}
/// <summary>
/// 查找类似 {0} : {1}/s↑ | {2}/s↓ 的本地化字符串。
/// </summary>
@@ -2562,6 +2589,25 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Certificate Pinning 的本地化字符串。
/// </summary>
public static string TbCertPinning {
get {
return ResourceManager.GetString("TbCertPinning", resourceCulture);
}
}
/// <summary>
/// 查找类似 Server certificate (PEM format, optional). Entering a certificate will pin it.
///Do not use the &quot;Fetch Certificate&quot; button when &quot;Allow Insecure&quot; is enabled. 的本地化字符串。
/// </summary>
public static string TbCertPinningTips {
get {
return ResourceManager.GetString("TbCertPinningTips", resourceCulture);
}
}
/// <summary>
/// 查找类似 Clear system proxy 的本地化字符串。
/// </summary>
@@ -2769,6 +2815,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Fetch Certificate 的本地化字符串。
/// </summary>
public static string TbFetchCert {
get {
return ResourceManager.GetString("TbFetchCert", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fetch Certificate Chain 的本地化字符串。
/// </summary>
public static string TbFetchCertChain {
get {
return ResourceManager.GetString("TbFetchCertChain", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fingerprint 的本地化字符串。
/// </summary>
@@ -2958,6 +3022,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Auto add filtered configuration from subscription groups 的本地化字符串。
/// </summary>
public static string TbPolicyGroupSubChildTip {
get {
return ResourceManager.GetString("TbPolicyGroupSubChildTip", resourceCulture);
}
}
/// <summary>
/// 查找类似 Policy Group Type 的本地化字符串。
/// </summary>

View File

@@ -1537,7 +1537,7 @@
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Server List</value>
<value>Configuration List</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
@@ -1599,4 +1599,29 @@
<data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
</root>

View File

@@ -1528,13 +1528,13 @@
<value>Ajouter une chaîne de proxy</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>Ajouter un enfant</value>
<value>Ajouter une sous-configuration</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>Supprimer lenfant</value>
<value>Supprimer une sous-configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Liste des enfants</value>
<value>Liste des configurations</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Basculement (failover)</value>
@@ -1596,4 +1596,29 @@
<data name="menuFastRealPing" xml:space="preserve">
<value>Test 1-clic de latence réelle</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Ajout auto des configs filtrées depuis les groupes dabonnement</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Certificat serveur (PEM, optionnel). Lajout dun certificat le fixe.
Ne pas utiliser « Obtenir le certificat » si « Autoriser non sécurisé » est activé.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Obtenir le certificat</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Obtenir la chaîne de certificats</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Veuillez définir un domaine valide</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificat non configuré</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificat configuré </value>
</data>
</root>

View File

@@ -1537,7 +1537,7 @@
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Server List</value>
<value>Configuration List</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
@@ -1599,4 +1599,29 @@
<data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
</root>

View File

@@ -1537,7 +1537,7 @@
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Server List</value>
<value>Configuration List</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
@@ -1599,4 +1599,29 @@
<data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
</root>

View File

@@ -1537,7 +1537,7 @@
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Server List</value>
<value>Configuration List</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
@@ -1599,4 +1599,29 @@
<data name="menuFastRealPing" xml:space="preserve">
<value>Test real delay</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
</root>

View File

@@ -1528,13 +1528,13 @@
<value>添加链式代理</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>添加子</value>
<value>添加子配置</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>删除子</value>
<value>删除子配置</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>子项列表</value>
<value>子配置项</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>故障转移</value>
@@ -1596,4 +1596,29 @@
<data name="menuFastRealPing" xml:space="preserve">
<value>一键测试真连接延迟</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>自动从订阅分组添加过滤后的配置</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>固定证书</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>服务器证书PEM 格式,可选)。填入后将固定该证书。
启用“跳过证书验证”时,请勿使用 '获取证书'。</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>获取证书</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>获取证书链</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>请设置有效的域名</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>证书未设置</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>证书已设置</value>
</data>
</root>

View File

@@ -1528,13 +1528,13 @@
<value>添加鏈式代理</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>添加子</value>
<value>添加子配置</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>刪除子</value>
<value>刪除子配置</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>子項清單</value>
<value>子配置項</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>容錯移轉</value>
@@ -1596,4 +1596,29 @@
<data name="menuFastRealPing" xml:space="preserve">
<value>一鍵測試真連線延遲</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>自動從訂閱分組新增過濾後的配置</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Server certificate (PEM format, optional). Entering a certificate will pin it.
Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
</root>

View File

@@ -202,7 +202,9 @@ public partial class CoreConfigSingboxService
var routing = await ConfigHandler.GetDefaultRouting(_config);
if (routing == null)
{
return 0;
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
var expectedIPCidr = new List<string>();

View File

@@ -204,54 +204,6 @@ public partial class CoreConfigSingboxService
return await Task.FromResult<BaseServer4Sbox?>(null);
}
private async Task<int> GenGroupOutbound(ProfileItem node, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false)
{
try
{
if (!node.ConfigType.IsGroupType())
{
return -1;
}
var hasCycle = ProfileGroupItemManager.HasCycle(node.IndexId);
if (hasCycle)
{
return -1;
}
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);
if (childProfiles.Count <= 0)
{
return -1;
}
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
if (ignoreOriginChain)
{
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
}
else
{
await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
}
break;
case EConfigType.ProxyChain:
await GenChainOutboundsList(childProfiles, singboxConfig, baseTagName);
break;
default:
break;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound)
{
try
@@ -280,7 +232,7 @@ public partial class CoreConfigSingboxService
{
try
{
if (node.StreamSecurity == Global.StreamSecurityReality || node.StreamSecurity == Global.StreamSecurity)
if (node.StreamSecurity is Global.StreamSecurityReality or Global.StreamSecurity)
{
var server_name = string.Empty;
if (node.Sni.IsNotEmpty())
@@ -307,7 +259,22 @@ public partial class CoreConfigSingboxService
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
};
}
if (node.StreamSecurity == Global.StreamSecurityReality)
if (node.StreamSecurity == Global.StreamSecurity)
{
var certs = node.Cert
?.Split("-----END CERTIFICATE-----", StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.TrimEx())
.Where(s => !s.IsNullOrEmpty())
.Select(s => s + "\n-----END CERTIFICATE-----")
.Select(s => s.Replace("\r\n", "\n"))
.ToList() ?? new();
if (certs.Count > 0)
{
tls.certificate = certs;
tls.insecure = false;
}
}
else if (node.StreamSecurity == Global.StreamSecurityReality)
{
tls.reality = new Reality4Sbox()
{
@@ -404,6 +371,54 @@ public partial class CoreConfigSingboxService
return await Task.FromResult(0);
}
private async Task<int> GenGroupOutbound(ProfileItem node, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false)
{
try
{
if (!node.ConfigType.IsGroupType())
{
return -1;
}
var hasCycle = ProfileGroupItemManager.HasCycle(node.IndexId);
if (hasCycle)
{
return -1;
}
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);
if (childProfiles.Count <= 0)
{
return -1;
}
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
if (ignoreOriginChain)
{
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
}
else
{
await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
}
break;
case EConfigType.ProxyChain:
await GenChainOutboundsList(childProfiles, singboxConfig, baseTagName);
break;
default:
break;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig)
{
if (node.Subid.IsNullOrEmpty())
@@ -668,7 +683,10 @@ public partial class CoreConfigSingboxService
{
var node = nodes[i];
if (node == null)
{
continue;
}
if (node.ConfigType.IsGroupType())
{
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);

View File

@@ -250,7 +250,9 @@ public partial class CoreConfigSingboxService
foreach (var it in item.Domain)
{
if (ParseV2Domain(it, rule1))
{
countDomain++;
}
}
if (countDomain > 0)
{
@@ -265,7 +267,9 @@ public partial class CoreConfigSingboxService
foreach (var it in item.Ip)
{
if (ParseV2Address(it, rule2))
{
countIp++;
}
}
if (countIp > 0)
{

View File

@@ -7,7 +7,9 @@ public partial class CoreConfigSingboxService
static void AddRuleSets(List<string> ruleSets, List<string>? rule_set)
{
if (rule_set != null)
{
ruleSets.AddRange(rule_set);
}
}
var geosite = "geosite";
var geoip = "geoip";

View File

@@ -137,7 +137,9 @@ public partial class CoreConfigV2rayService(Config config)
foreach (var rule in rules)
{
if (rule.outboundTag == null)
{
continue;
}
if (balancerTagSet.Contains(rule.outboundTag))
{

View File

@@ -11,7 +11,9 @@ public partial class CoreConfigV2rayService
// Case 1: exact match already exists -> nothing to do
if (subjectSelectors.Any(baseTagName.StartsWith))
{
return await Task.FromResult(0);
}
// Case 2: prefix match exists -> reuse it and move to the first position
var matched = subjectSelectors.FirstOrDefault(s => s.StartsWith(baseTagName));

View File

@@ -189,7 +189,10 @@ public partial class CoreConfigV2rayService
foreach (var domain in item.Domain)
{
if (domain.StartsWith('#'))
{
continue;
}
var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ",");
if (item.OutboundTag == Global.DirectTag)
@@ -368,7 +371,10 @@ public partial class CoreConfigV2rayService
foreach (var host in systemHosts)
{
if (normalHost1[host.Key] != null)
{
continue;
}
normalHost1[host.Key] = host.Value;
}
}

View File

@@ -245,6 +245,13 @@ public partial class CoreConfigV2rayService
var host = node.RequestHost.TrimEx();
var path = node.Path.TrimEx();
var sni = node.Sni.TrimEx();
var certs = node.Cert
?.Split("-----END CERTIFICATE-----", StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.TrimEx())
.Where(s => !s.IsNullOrEmpty())
.Select(s => s + "\n-----END CERTIFICATE-----")
.Select(s => s.Replace("\r\n", "\n"))
.ToList() ?? new();
var useragent = "";
if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty())
{
@@ -277,6 +284,22 @@ public partial class CoreConfigV2rayService
{
tlsSettings.serverName = Utils.String2List(host)?.First();
}
if (certs.Count > 0)
{
var certsettings = new List<CertificateSettings4Ray>();
foreach (var cert in certs)
{
var certPerLine = cert.Split("\n").ToList();
certsettings.Add(new CertificateSettings4Ray
{
certificate = certPerLine,
usage = "verify",
});
}
tlsSettings.certificates = certsettings;
tlsSettings.disableSystemRoot = true;
tlsSettings.allowInsecure = false;
}
streamSettings.tlsSettings = tlsSettings;
}
@@ -781,7 +804,10 @@ public partial class CoreConfigV2rayService
{
var node = nodes[i];
if (node == null)
{
continue;
}
if (node.ConfigType.IsGroupType())
{
var (childProfiles, _) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);

View File

@@ -71,7 +71,7 @@ public class DownloadService
AllowAutoRedirect = false,
Proxy = await GetWebProxy(blProxy)
};
HttpClient client = new(webRequestHandler);
var client = new HttpClient(webRequestHandler);
var response = await client.GetAsync(url);
if (response.StatusCode == HttpStatusCode.Redirect && response.Headers.Location is not null)
@@ -156,7 +156,7 @@ public class DownloadService
}
using var cts = new CancellationTokenSource();
var result = await HttpClientHelper.Instance.GetAsync(client, url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
var result = await client.GetStringAsync(url, cts.Token).WaitAsync(TimeSpan.FromSeconds(timeout), cts.Token);
return result;
}
catch (Exception ex)

View File

@@ -21,7 +21,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{
if (_lstExitLoop.Count > 0)
{
UpdateFunc("", ResUI.SpeedtestingStop);
_ = UpdateFunc("", ResUI.SpeedtestingStop);
_lstExitLoop.Clear();
}
@@ -272,7 +272,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private async Task<int> DoRealPing(ServerTestItem it)
{
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
var responseTime = await HttpClientHelper.Instance.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
var responseTime = await ConnectionHandler.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
await UpdateFunc(it.IndexId, responseTime.ToString());

View File

@@ -1,14 +1,14 @@
namespace ServiceLib.Services;
public class UpdateService
public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
{
private Func<bool, string, Task>? _updateFunc;
private readonly Config? _config = config;
private readonly Func<bool, string, Task>? _updateFunc = updateFunc;
private readonly int _timeout = 30;
private static readonly string _tag = "UpdateService";
public async Task CheckUpdateGuiN(Config config, Func<bool, string, Task> updateFunc, bool preRelease)
public async Task CheckUpdateGuiN(bool preRelease)
{
_updateFunc = updateFunc;
var url = string.Empty;
var fileName = string.Empty;
@@ -47,9 +47,8 @@ public class UpdateService
}
}
public async Task CheckUpdateCore(ECoreType type, Config config, Func<bool, string, Task> updateFunc, bool preRelease)
public async Task CheckUpdateCore(ECoreType type, bool preRelease)
{
_updateFunc = updateFunc;
var url = string.Empty;
var fileName = string.Empty;
@@ -101,11 +100,11 @@ public class UpdateService
}
}
public async Task UpdateGeoFileAll(Config config, Func<bool, string, Task> updateFunc)
public async Task UpdateGeoFileAll()
{
await UpdateGeoFiles(config, updateFunc);
await UpdateOtherFiles(config, updateFunc);
await UpdateSrsFileAll(config, updateFunc);
await UpdateGeoFiles();
await UpdateOtherFiles();
await UpdateSrsFileAll();
await UpdateFunc(true, string.Format(ResUI.MsgDownloadGeoFileSuccessfully, "geo"));
}
@@ -330,13 +329,11 @@ public class UpdateService
#region Geo private
private async Task UpdateGeoFiles(Config config, Func<bool, string, Task> updateFunc)
private async Task UpdateGeoFiles()
{
_updateFunc = updateFunc;
var geoUrl = string.IsNullOrEmpty(config?.ConstItem.GeoSourceUrl)
var geoUrl = string.IsNullOrEmpty(_config?.ConstItem.GeoSourceUrl)
? Global.GeoUrl
: config.ConstItem.GeoSourceUrl;
: _config.ConstItem.GeoSourceUrl;
List<string> files = ["geosite", "geoip"];
foreach (var geoName in files)
@@ -345,33 +342,29 @@ public class UpdateService
var targetPath = Utils.GetBinPath($"{fileName}");
var url = string.Format(geoUrl, geoName);
await DownloadGeoFile(url, fileName, targetPath, updateFunc);
await DownloadGeoFile(url, fileName, targetPath);
}
}
private async Task UpdateOtherFiles(Config config, Func<bool, string, Task> updateFunc)
private async Task UpdateOtherFiles()
{
//If it is not in China area, no update is required
if (config.ConstItem.GeoSourceUrl.IsNotEmpty())
if (_config.ConstItem.GeoSourceUrl.IsNotEmpty())
{
return;
}
_updateFunc = updateFunc;
foreach (var url in Global.OtherGeoUrls)
{
var fileName = Path.GetFileName(url);
var targetPath = Utils.GetBinPath($"{fileName}");
await DownloadGeoFile(url, fileName, targetPath, updateFunc);
await DownloadGeoFile(url, fileName, targetPath);
}
}
private async Task UpdateSrsFileAll(Config config, Func<bool, string, Task> updateFunc)
private async Task UpdateSrsFileAll()
{
_updateFunc = updateFunc;
var geoipFiles = new List<string>();
var geoSiteFiles = new List<string>();
@@ -414,29 +407,29 @@ public class UpdateService
}
foreach (var item in geoipFiles.Distinct())
{
await UpdateSrsFile("geoip", item, config, updateFunc);
await UpdateSrsFile("geoip", item);
}
foreach (var item in geoSiteFiles.Distinct())
{
await UpdateSrsFile("geosite", item, config, updateFunc);
await UpdateSrsFile("geosite", item);
}
}
private async Task UpdateSrsFile(string type, string srsName, Config config, Func<bool, string, Task> updateFunc)
private async Task UpdateSrsFile(string type, string srsName)
{
var srsUrl = string.IsNullOrEmpty(config.ConstItem.SrsSourceUrl)
var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl)
? Global.SingboxRulesetUrl
: config.ConstItem.SrsSourceUrl;
: _config.ConstItem.SrsSourceUrl;
var fileName = $"{type}-{srsName}.srs";
var targetPath = Path.Combine(Utils.GetBinPath("srss"), fileName);
var url = string.Format(srsUrl, type, $"{type}-{srsName}", srsName);
await DownloadGeoFile(url, fileName, targetPath, updateFunc);
await DownloadGeoFile(url, fileName, targetPath);
}
private async Task DownloadGeoFile(string url, string fileName, string targetPath, Func<bool, string, Task> updateFunc)
private async Task DownloadGeoFile(string url, string fileName, string targetPath)
{
var tmpFileName = Utils.GetTempPath(Utils.GetGuid());

View File

@@ -0,0 +1,171 @@
namespace ServiceLib.Services;
/// <summary>
/// http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
/// </summary>
public sealed class WindowsJobService : IDisposable
{
private nint handle = nint.Zero;
public WindowsJobService()
{
handle = CreateJobObject(nint.Zero, null);
var extendedInfoPtr = nint.Zero;
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
{
LimitFlags = 0x2000
};
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
BasicLimitInformation = info
};
try
{
var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr,
(uint)length))
{
throw new Exception(string.Format("Unable to set information. Error: {0}",
Marshal.GetLastWin32Error()));
}
}
finally
{
if (extendedInfoPtr != nint.Zero)
{
Marshal.FreeHGlobal(extendedInfoPtr);
}
}
}
public bool AddProcess(nint processHandle)
{
var succ = AssignProcessToJobObject(handle, processHandle);
if (!succ)
{
Logging.SaveLog("Failed to call AssignProcessToJobObject! GetLastError=" + Marshal.GetLastWin32Error());
}
return succ;
}
public bool AddProcess(int processId)
{
return AddProcess(Process.GetProcessById(processId).Handle);
}
#region IDisposable
private bool disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposed)
{
return;
}
disposed = true;
if (disposing)
{
// no managed objects to free
}
if (handle != nint.Zero)
{
CloseHandle(handle);
handle = nint.Zero;
}
}
~WindowsJobService()
{
Dispose(false);
}
#endregion IDisposable
#region Interop
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern nint CreateJobObject(nint a, string? lpName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetInformationJobObject(nint hJob, JobObjectInfoType infoType, nint lpJobObjectInfo, uint cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AssignProcessToJobObject(nint job, nint process);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(nint hObject);
#endregion Interop
}
[StructLayout(LayoutKind.Sequential)]
internal struct IO_COUNTERS
{
public ulong ReadOperationCount;
public ulong WriteOperationCount;
public ulong OtherOperationCount;
public ulong ReadTransferCount;
public ulong WriteTransferCount;
public ulong OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public long PerProcessUserTimeLimit;
public long PerJobUserTimeLimit;
public uint LimitFlags;
public nuint MinimumWorkingSetSize;
public nuint MaximumWorkingSetSize;
public uint ActiveProcessLimit;
public nuint Affinity;
public uint PriorityClass;
public uint SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public uint nLength;
public nint lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public nuint ProcessMemoryLimit;
public nuint JobMemoryLimit;
public nuint PeakProcessMemoryUsed;
public nuint PeakJobMemoryUsed;
}
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}

View File

@@ -17,6 +17,14 @@ public class AddGroupServerViewModel : MyReactiveObject
[Reactive]
public string? PolicyGroupType { get; set; }
[Reactive]
public SubItem? SelectedSubItem { get; set; }
[Reactive]
public string? Filter { get; set; }
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
public IObservableCollection<ProfileItem> ChildItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
//public ReactiveCommand<Unit, Unit> AddCmd { get; }
@@ -64,10 +72,14 @@ public class AddGroupServerViewModel : MyReactiveObject
});
SelectedSource = profileItem.IndexId.IsNullOrEmpty() ? profileItem : JsonUtils.DeepCopy(profileItem);
CoreType = (SelectedSource?.CoreType ?? ECoreType.Xray).ToString();
ProfileGroupItemManager.Instance.TryGet(profileItem.IndexId, out var profileGroup);
_ = Init();
}
public async Task Init()
{
ProfileGroupItemManager.Instance.TryGet(SelectedSource.IndexId, out var profileGroup);
PolicyGroupType = (profileGroup?.MultipleLoad ?? EMultipleLoad.LeastPing) switch
{
EMultipleLoad.LeastPing => ResUI.TbLeastPing,
@@ -78,15 +90,16 @@ public class AddGroupServerViewModel : MyReactiveObject
_ => ResUI.TbLeastPing,
};
_ = Init();
}
var subs = await AppManager.Instance.SubItems();
subs.Add(new SubItem());
SubItems.AddRange(subs);
SelectedSubItem = SubItems.Where(s => s.Id == profileGroup?.SubChildItems).FirstOrDefault();
Filter = profileGroup?.Filter;
public async Task Init()
{
var childItemMulti = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource?.IndexId);
if (childItemMulti != null)
{
var childIndexIds = childItemMulti.ChildItems.IsNullOrEmpty() ? new List<string>() : Utils.String2List(childItemMulti.ChildItems);
var childIndexIds = Utils.String2List(childItemMulti.ChildItems) ?? [];
foreach (var item in childIndexIds)
{
var child = await AppManager.Instance.GetProfileItem(item);
@@ -181,7 +194,7 @@ public class AddGroupServerViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
return;
}
if (ChildItemsObs.Count == 0)
if (ChildItemsObs.Count == 0 && SelectedSubItem?.Id.IsNullOrEmpty() == true)
{
NoticeManager.Instance.Enqueue(ResUI.PleaseAddAtLeastOneServer);
return;
@@ -213,6 +226,9 @@ public class AddGroupServerViewModel : MyReactiveObject
_ => EMultipleLoad.LeastPing,
};
profileGroup.SubChildItems = SelectedSubItem?.Id;
profileGroup.Filter = Filter;
var hasCycle = ProfileGroupItemManager.HasCycle(profileGroup.IndexId);
if (hasCycle)
{

View File

@@ -2,12 +2,22 @@ namespace ServiceLib.ViewModels;
public class AddServerViewModel : MyReactiveObject
{
private string _certError = string.Empty;
[Reactive]
public ProfileItem SelectedSource { get; set; }
[Reactive]
public string? CoreType { get; set; }
[Reactive]
public string Cert { get; set; }
[Reactive]
public string CertTip { get; set; }
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
@@ -15,11 +25,22 @@ public class AddServerViewModel : MyReactiveObject
_config = AppManager.Instance.Config;
_updateView = updateView;
FetchCertCmd = ReactiveCommand.CreateFromTask(async () =>
{
await FetchCert();
});
FetchCertChainCmd = ReactiveCommand.CreateFromTask(async () =>
{
await FetchCertChain();
});
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
{
await SaveServerAsync();
});
this.WhenAnyValue(x => x.Cert)
.Subscribe(_ => UpdateCertTip());
if (profileItem.IndexId.IsNullOrEmpty())
{
profileItem.Network = Global.DefaultNetwork;
@@ -33,6 +54,7 @@ public class AddServerViewModel : MyReactiveObject
SelectedSource = JsonUtils.DeepCopy(profileItem);
}
CoreType = SelectedSource?.CoreType?.ToString();
Cert = SelectedSource?.Cert?.ToString() ?? string.Empty;
}
private async Task SaveServerAsync()
@@ -77,6 +99,7 @@ public class AddServerViewModel : MyReactiveObject
}
}
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
SelectedSource.Cert = Cert.IsNullOrEmpty() ? null : Cert;
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
{
@@ -88,4 +111,72 @@ public class AddServerViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
}
}
private void UpdateCertTip()
{
CertTip = _certError.IsNullOrEmpty()
? (Cert.IsNullOrEmpty() ? ResUI.CertNotSet : ResUI.CertSet)
: _certError;
}
private async Task FetchCert()
{
if (SelectedSource.StreamSecurity != Global.StreamSecurity)
{
return;
}
var domain = SelectedSource.Address;
var serverName = SelectedSource.Sni;
if (serverName.IsNullOrEmpty())
{
serverName = SelectedSource.RequestHost;
}
if (serverName.IsNullOrEmpty())
{
serverName = SelectedSource.Address;
}
if (!Utils.IsDomain(serverName))
{
_certError = ResUI.ServerNameMustBeValidDomain;
UpdateCertTip();
_certError = string.Empty;
return;
}
if (SelectedSource.Port > 0)
{
domain += $":{SelectedSource.Port}";
}
Cert = await CertPemManager.Instance.GetCertPemAsync(domain, serverName);
}
private async Task FetchCertChain()
{
if (SelectedSource.StreamSecurity != Global.StreamSecurity)
{
return;
}
var domain = SelectedSource.Address;
var serverName = SelectedSource.Sni;
if (serverName.IsNullOrEmpty())
{
serverName = SelectedSource.RequestHost;
}
if (serverName.IsNullOrEmpty())
{
serverName = SelectedSource.Address;
}
if (!Utils.IsDomain(serverName))
{
_certError = ResUI.ServerNameMustBeValidDomain;
UpdateCertTip();
_certError = string.Empty;
return;
}
if (SelectedSource.Port > 0)
{
domain += $":{SelectedSource.Port}";
}
var certs = await CertPemManager.Instance.GetCertChainPemAsync(domain, serverName);
Cert = string.Join("\n", certs);
}
}

View File

@@ -119,7 +119,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
return;
}
//check
var lstFiles = FileManager.GetFilesFromZip(fileName);
var lstFiles = FileUtils.GetFilesFromZip(fileName);
if (lstFiles is null || !lstFiles.Any(t => t.Contains(_guiConfigs)))
{
DisplayOperationMsg(ResUI.LocalRestoreInvalidZipTips);
@@ -135,7 +135,7 @@ public class BackupAndRestoreViewModel : MyReactiveObject
await SQLiteHelper.Instance.DisposeDbConnectionAsync();
var toPath = Utils.GetConfigPath();
FileManager.ZipExtractToFile(fileName, toPath, "");
FileUtils.ZipExtractToFile(fileName, toPath, "");
if (Utils.IsWindows())
{
@@ -167,8 +167,8 @@ public class BackupAndRestoreViewModel : MyReactiveObject
var configDirZipTemp = Utils.GetTempPath($"v2rayN_{DateTime.Now:yyyyMMddHHmmss}");
var configDirTemp = Path.Combine(configDirZipTemp, _guiConfigs);
FileManager.CopyDirectory(configDir, configDirTemp, false, true, "");
var ret = FileManager.CreateFromDirectory(configDirZipTemp, fileName);
FileUtils.CopyDirectory(configDir, configDirTemp, false, true, "");
var ret = FileUtils.CreateFromDirectory(configDirZipTemp, fileName);
Directory.Delete(configDirZipTemp, true);
return await Task.FromResult(ret);
}

View File

@@ -148,7 +148,7 @@ public class CheckUpdateViewModel : MyReactiveObject
UpdatedPlusPlus(_geo, "");
}
}
await new UpdateService().UpdateGeoFileAll(_config, _updateUI)
await new UpdateService(_config, _updateUI).UpdateGeoFileAll()
.ContinueWith(t => UpdatedPlusPlus(_geo, ""));
}
@@ -163,7 +163,7 @@ public class CheckUpdateViewModel : MyReactiveObject
UpdatedPlusPlus(_v2rayN, msg);
}
}
await new UpdateService().CheckUpdateGuiN(_config, _updateUI, preRelease)
await new UpdateService(_config, _updateUI).CheckUpdateGuiN(preRelease)
.ContinueWith(t => UpdatedPlusPlus(_v2rayN, ""));
}
@@ -180,7 +180,7 @@ public class CheckUpdateViewModel : MyReactiveObject
}
}
var type = (ECoreType)Enum.Parse(typeof(ECoreType), model.CoreType);
await new UpdateService().CheckUpdateCore(type, _config, _updateUI, preRelease)
await new UpdateService(_config, _updateUI).CheckUpdateCore(type, preRelease)
.ContinueWith(t => UpdatedPlusPlus(model.CoreType, ""));
}
@@ -209,6 +209,7 @@ public class CheckUpdateViewModel : MyReactiveObject
_ = UpdateFinishedResult(blReload);
return Disposable.Empty;
});
await Task.CompletedTask;
}
public async Task UpdateFinishedResult(bool blReload)
@@ -270,24 +271,24 @@ public class CheckUpdateViewModel : MyReactiveObject
if (fileName.Contains(".tar.gz"))
{
FileManager.DecompressTarFile(fileName, toPath);
FileUtils.DecompressTarFile(fileName, toPath);
var dir = new DirectoryInfo(toPath);
if (dir.Exists)
{
foreach (var subDir in dir.GetDirectories())
{
FileManager.CopyDirectory(subDir.FullName, toPath, false, true);
FileUtils.CopyDirectory(subDir.FullName, toPath, false, true);
subDir.Delete(true);
}
}
}
else if (fileName.Contains(".gz"))
{
FileManager.DecompressFile(fileName, toPath, item.CoreType);
FileUtils.DecompressFile(fileName, toPath, item.CoreType);
}
else
{
FileManager.ZipExtractToFile(fileName, toPath, "geo");
FileUtils.ZipExtractToFile(fileName, toPath, "geo");
}
if (Utils.IsNonWindows())
@@ -321,6 +322,7 @@ public class CheckUpdateViewModel : MyReactiveObject
_ = UpdateViewResult(model);
return Disposable.Empty;
});
await Task.CompletedTask;
}
public async Task UpdateViewResult(CheckUpdateModel model)
@@ -331,5 +333,6 @@ public class CheckUpdateViewModel : MyReactiveObject
return;
}
found.Remarks = model.Remarks;
await Task.CompletedTask;
}
}

View File

@@ -96,6 +96,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
}
ConnectionItems.AddRange(lstModel);
await Task.CompletedTask;
}
public async Task ClashConnectionClose(bool all)

View File

@@ -245,6 +245,7 @@ public class ClashProxiesViewModel : MyReactiveObject
{
SelectedGroup = new();
}
await Task.CompletedTask;
}
private void RefreshProxyDetails(bool c)
@@ -391,6 +392,7 @@ public class ClashProxiesViewModel : MyReactiveObject
_ = ProxiesDelayTestResult(model);
return Disposable.Empty;
});
await Task.CompletedTask;
});
await Task.CompletedTask;
}
@@ -419,6 +421,7 @@ public class ClashProxiesViewModel : MyReactiveObject
detail.Delay = _delayTimeout;
detail.DelayName = string.Empty;
}
await Task.CompletedTask;
}
#endregion proxy function

View File

@@ -66,10 +66,14 @@ public class FullConfigTemplateViewModel : MyReactiveObject
private async Task SaveSettingAsync()
{
if (!await SaveXrayConfigAsync())
{
return;
}
if (!await SaveSingboxConfigAsync())
{
return;
}
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
_ = _updateView?.Invoke(EViewAction.CloseWindow, null);

View File

@@ -78,55 +78,55 @@ public class MainWindowViewModel : MyReactiveObject
//servers
AddVmessServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.VMess);
await AddServerAsync(EConfigType.VMess);
});
AddVlessServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.VLESS);
await AddServerAsync(EConfigType.VLESS);
});
AddShadowsocksServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.Shadowsocks);
await AddServerAsync(EConfigType.Shadowsocks);
});
AddSocksServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.SOCKS);
await AddServerAsync(EConfigType.SOCKS);
});
AddHttpServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.HTTP);
await AddServerAsync(EConfigType.HTTP);
});
AddTrojanServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.Trojan);
await AddServerAsync(EConfigType.Trojan);
});
AddHysteria2ServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.Hysteria2);
await AddServerAsync(EConfigType.Hysteria2);
});
AddTuicServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.TUIC);
await AddServerAsync(EConfigType.TUIC);
});
AddWireguardServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.WireGuard);
await AddServerAsync(EConfigType.WireGuard);
});
AddAnytlsServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.Anytls);
await AddServerAsync(EConfigType.Anytls);
});
AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.Custom);
await AddServerAsync(EConfigType.Custom);
});
AddPolicyGroupServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.PolicyGroup);
await AddServerAsync(EConfigType.PolicyGroup);
});
AddProxyChainServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.ProxyChain);
await AddServerAsync(EConfigType.ProxyChain);
});
AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () =>
{
@@ -283,6 +283,7 @@ public class MainWindowViewModel : MyReactiveObject
{
NoticeManager.Instance.Enqueue(msg);
}
await Task.CompletedTask;
}
private async Task UpdateTaskHandler(bool success, string msg)
@@ -310,6 +311,7 @@ public class MainWindowViewModel : MyReactiveObject
return;
}
AppEvents.DispatcherStatisticsRequested.Publish(update);
await Task.CompletedTask;
}
#endregion Actions
@@ -332,7 +334,7 @@ public class MainWindowViewModel : MyReactiveObject
#region Add Servers
public async Task AddServerAsync(bool blNew, EConfigType eConfigType)
public async Task AddServerAsync(EConfigType eConfigType)
{
ProfileItem item = new()
{
@@ -594,7 +596,7 @@ public class MainWindowViewModel : MyReactiveObject
AppEvents.RoutingsMenuRefreshRequested.Publish();
await ConfigHandler.SaveConfig(_config);
await new UpdateService().UpdateGeoFileAll(_config, UpdateTaskHandler);
await new UpdateService(_config, UpdateTaskHandler).UpdateGeoFileAll();
await Reload();
}

View File

@@ -110,7 +110,7 @@ public class ProfilesViewModel : MyReactiveObject
//servers delete
EditServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await EditServerAsync(EConfigType.Custom);
await EditServerAsync();
}, canEditRemove);
RemoveServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
@@ -300,14 +300,14 @@ public class ProfilesViewModel : MyReactiveObject
if (result.Delay.IsNotEmpty())
{
int.TryParse(result.Delay, out var temp);
item.Delay = temp;
item.Delay = result.Delay.ToInt();
item.DelayVal = result.Delay ?? string.Empty;
}
if (result.Speed.IsNotEmpty())
{
item.SpeedVal = result.Speed ?? string.Empty;
}
await Task.CompletedTask;
}
public async Task UpdateStatistics(ServerSpeedItem update)
@@ -333,6 +333,7 @@ public class ProfilesViewModel : MyReactiveObject
catch
{
}
await Task.CompletedTask;
}
#endregion Actions
@@ -487,7 +488,7 @@ public class ProfilesViewModel : MyReactiveObject
return lstSelected;
}
public async Task EditServerAsync(EConfigType eConfigType)
public async Task EditServerAsync()
{
if (string.IsNullOrEmpty(SelectedProfile?.IndexId))
{
@@ -499,7 +500,7 @@ public class ProfilesViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
return;
}
eConfigType = item.ConfigType;
var eConfigType = item.ConfigType;
bool? ret = false;
if (eConfigType == EConfigType.Custom)
@@ -753,6 +754,7 @@ public class ProfilesViewModel : MyReactiveObject
_ = SetSpeedTestResult(result);
return Disposable.Empty;
});
await Task.CompletedTask;
});
_speedtestService?.RunLoop(actionType, lstSelected);
}

View File

@@ -120,7 +120,7 @@ public class StatusBarViewModel : MyReactiveObject
this.WhenAnyValue(
x => x.SelectedServer,
y => y != null && !y.Text.IsNullOrEmpty())
.Subscribe(c => ServerSelectedChanged(c));
.Subscribe(ServerSelectedChanged);
SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType;
this.WhenAnyValue(
@@ -367,11 +367,13 @@ public class StatusBarViewModel : MyReactiveObject
_ = TestServerAvailabilityResult(msg);
return Disposable.Empty;
});
await Task.CompletedTask;
}
public async Task TestServerAvailabilityResult(string msg)
{
RunningInfoDisplay = msg;
await Task.CompletedTask;
}
#region System proxy and Routings
@@ -384,7 +386,7 @@ public class StatusBarViewModel : MyReactiveObject
}
_config.SystemProxyItem.SysProxyType = type;
await ChangeSystemProxyAsync(type, true);
NoticeManager.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType.ToString()}");
NoticeManager.Instance.SendMessageEx($"{ResUI.TipChangeSystemProxy} - {_config.SystemProxyItem.SysProxyType}");
SystemProxySelected = (int)_config.SystemProxyItem.SysProxyType;
await ConfigHandler.SaveConfig(_config);
@@ -561,6 +563,7 @@ public class StatusBarViewModel : MyReactiveObject
catch
{
}
await Task.CompletedTask;
}
#endregion UI

View File

@@ -28,7 +28,10 @@ internal class AvaUtils
{
var clipboard = TopLevel.GetTopLevel(visual)?.Clipboard;
if (clipboard == null)
{
return;
}
await clipboard.SetTextAsync(strData);
}
catch

View File

@@ -6,7 +6,7 @@ public class DelayColorConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
_ = int.TryParse(value?.ToString(), out var delay);
var delay = value.ToString().ToInt();
return delay switch
{

View File

@@ -95,7 +95,9 @@ public class ThemeSettingViewModel : MyReactiveObject
{
double size = CurrentFontSize;
if (size < Global.MinFontSize)
{
return;
}
Style style = new(x => Selectors.Or(
x.OfType<Button>(),

View File

@@ -33,63 +33,106 @@
IsCancel="True" />
</StackPanel>
<Grid DockPanel.Dock="Top" RowDefinitions="Auto,*,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Grid
Grid.Row="0"
ColumnDefinitions="180,Auto,Auto"
DockPanel.Dock="Top"
RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="{StaticResource Margin4}"
Text="{x:Static resx:ResUI.menuServers}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRemarks}" />
<TextBox
x:Name="txtRemarks"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCoreType}" />
<ComboBox
x:Name="cmbCoreType"
Grid.Row="2"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<Grid
Grid.Row="0"
ColumnDefinitions="180,Auto,Auto"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
x:Name="gridPolicyGroup"
Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="3"
ColumnDefinitions="180,Auto,Auto">
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="{StaticResource Margin4}"
Text="{x:Static resx:ResUI.menuServers}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRemarks}" />
<TextBox
x:Name="txtRemarks"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCoreType}" />
Text="{x:Static resx:ResUI.TbPolicyGroupType}" />
<ComboBox
x:Name="cmbCoreType"
Grid.Row="2"
x:Name="cmbPolicyGroupType"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
</Grid>
</Grid>
<TabControl DockPanel.Dock="Top">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<Grid
x:Name="gridPolicyGroup"
Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="3"
ColumnDefinitions="180,Auto,Auto">
Margin="{StaticResource Margin8}"
ColumnDefinitions="180,Auto,Auto"
RowDefinitions="Auto,Auto,Auto">
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPolicyGroupType}" />
Text="{x:Static resx:ResUI.menuSubscription}" />
<ComboBox
x:Name="cmbPolicyGroupType"
x:Name="cmbSubChildItems"
Grid.Row="0"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
Margin="{StaticResource Margin4}"
DisplayMemberBinding="{Binding Remarks}"
ItemsSource="{Binding SubItems}" />
<TextBlock
Grid.Row="0"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.LvFilter}" />
<TextBox
x:Name="txtFilter"
Grid.Row="1"
Grid.Column="1"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center" />
</Grid>
</Grid>
</Grid>
</TabItem>
</TabControl>
<TabControl>
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<DataGrid

View File

@@ -46,6 +46,9 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.SelectedValue).DisposeWith(disposables);
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.ChildItemsObs, v => v.lstChild.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables);
@@ -92,7 +95,9 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
private void AddGroupServerWindow_KeyDown(object? sender, KeyEventArgs e)
{
if (!lstChild.IsKeyboardFocusWithin)
{
return;
}
if ((e.KeyModifiers & (KeyModifiers.Control | KeyModifiers.Meta)) != 0)
{

View File

@@ -607,10 +607,10 @@
<Button
x:Name="btnExtra"
Classes="IconButton"
Margin="{StaticResource MarginLr8}">
Margin="{StaticResource MarginLr8}"
Classes="IconButton">
<Button.Content>
<PathIcon Data="{StaticResource SemiIconMore}" >
<PathIcon Data="{StaticResource SemiIconMore}">
<PathIcon.RenderTransform>
<RotateTransform Angle="90" />
</PathIcon.RenderTransform>
@@ -713,7 +713,7 @@
Grid.Row="7"
ColumnDefinitions="180,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
@@ -767,6 +767,67 @@
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertPinning}" />
<StackPanel
Grid.Row="5"
Grid.Column="1"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
x:Name="labCertPinning"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center" />
<Button
Margin="{StaticResource MarginLr4}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Classes="IconButton">
<Button.Content>
<PathIcon Data="{StaticResource SemiIconMore}">
<PathIcon.RenderTransform>
<RotateTransform Angle="90" />
</PathIcon.RenderTransform>
</PathIcon>
</Button.Content>
<Button.Flyout>
<Flyout>
<StackPanel>
<TextBlock
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertPinningTips}" />
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<Button
x:Name="btnFetchCert"
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCert}" />
<Button
x:Name="btnFetchCertChain"
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCertChain}" />
</StackPanel>
<TextBox
x:Name="txtCert"
Width="400"
MinHeight="100"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Classes="TextArea"
MinLines="6"
TextWrapping="Wrap" />
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</StackPanel>
</Grid>
<Grid
x:Name="gridRealityMore"

View File

@@ -185,6 +185,8 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.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.Cert, v => v.txtCert.Text).DisposeWith(disposables);
//reality
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);
@@ -193,6 +195,8 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
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.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);
});

View File

@@ -22,8 +22,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
_manager = new WindowNotificationManager(TopLevel.GetTopLevel(this)) { MaxItems = 3, Position = NotificationPosition.TopRight };
KeyDown += MainWindow_KeyDown;
menuSettingsSetUWP.Click += menuSettingsSetUWP_Click;
menuPromotion.Click += menuPromotion_Click;
menuSettingsSetUWP.Click += MenuSettingsSetUWP_Click;
menuPromotion.Click += MenuPromotion_Click;
menuCheckUpdate.Click += MenuCheckUpdate_Click;
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
menuClose.Click += MenuClose_Click;
@@ -188,6 +188,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
private async Task DelegateSnackMsg(string content)
{
_manager?.Show(new Notification(null, content, NotificationType.Information));
await Task.CompletedTask;
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
@@ -196,17 +197,26 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
{
case EViewAction.AddServerWindow:
if (obj is null)
{
return false;
}
return await new AddServerWindow((ProfileItem)obj).ShowDialog<bool>(this);
case EViewAction.AddServer2Window:
if (obj is null)
{
return false;
}
return await new AddServer2Window((ProfileItem)obj).ShowDialog<bool>(this);
case EViewAction.AddGroupServerWindow:
if (obj is null)
{
return false;
}
return await new AddGroupServerWindow((ProfileItem)obj).ShowDialog<bool>(this);
case EViewAction.DNSSettingWindow:
@@ -308,12 +318,12 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
}
}
private void menuPromotion_Click(object? sender, RoutedEventArgs e)
private void MenuPromotion_Click(object? sender, RoutedEventArgs e)
{
ProcUtils.ProcessStart($"{Utils.Base64Decode(Global.PromotionUrl)}?t={DateTime.Now.Ticks}");
}
private void menuSettingsSetUWP_Click(object? sender, RoutedEventArgs e)
private void MenuSettingsSetUWP_Click(object? sender, RoutedEventArgs e)
{
ProcUtils.ProcessStart(Utils.GetBinPath("EnableLoopback.exe"));
}
@@ -478,8 +488,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
{
var coreInfo = CoreInfoManager.Instance.GetCoreInfo();
foreach (var it in coreInfo
.Where(t => t.CoreType != ECoreType.v2fly
&& t.CoreType != ECoreType.hysteria))
.Where(t => t.CoreType is not ECoreType.v2fly
and not ECoreType.hysteria))
{
var item = new MenuItem()
{

View File

@@ -30,7 +30,9 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
{
case EViewAction.DispatcherShowMsg:
if (obj is null)
{
return false;
}
Dispatcher.UIThread.Post(() => ShowMsg(obj),
DispatcherPriority.ApplicationIdle);

View File

@@ -90,7 +90,7 @@ public partial class ProfilesSelectWindow : WindowBase<ProfilesSelectViewModel>
private void LstProfiles_DoubleTapped(object? sender, TappedEventArgs e)
{
// 忽略表头区域的双击
// Ignore double-taps on the column header area
if (e.Source is Control src)
{
if (src.FindAncestorOfType<DataGridColumnHeader>() != null)
@@ -99,7 +99,7 @@ public partial class ProfilesSelectWindow : WindowBase<ProfilesSelectViewModel>
return;
}
// 仅当在数据行或其子元素上双击时才触发选择
// Only trigger selection when double-tapped on a data row or its child element
if (src.FindAncestorOfType<DataGridRow>() != null)
{
ViewModel?.SelectFinish();
@@ -110,7 +110,7 @@ public partial class ProfilesSelectWindow : WindowBase<ProfilesSelectViewModel>
private void LstProfiles_Sorting(object? sender, DataGridColumnEventArgs e)
{
// 自定义排序,防止默认行为导致误触发
// Custom sort to prevent unintended default behavior
e.Handled = true;
if (ViewModel != null && e.Column?.Tag?.ToString() != null)
{

View File

@@ -124,7 +124,10 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
{
case EViewAction.SetClipboardData:
if (obj is null)
{
return false;
}
await AvaUtils.SetClipboardData(this, (string)obj);
break;
@@ -141,7 +144,10 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
case EViewAction.SaveFileDialog:
if (obj is null)
{
return false;
}
var fileName = await UI.SaveFileDialog(_window, "");
if (fileName.IsNullOrEmpty())
{
@@ -152,28 +158,43 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
case EViewAction.AddServerWindow:
if (obj is null)
{
return false;
}
return await new AddServerWindow((ProfileItem)obj).ShowDialog<bool>(_window);
case EViewAction.AddServer2Window:
if (obj is null)
{
return false;
}
return await new AddServer2Window((ProfileItem)obj).ShowDialog<bool>(_window);
case EViewAction.AddGroupServerWindow:
if (obj is null)
{
return false;
}
return await new AddGroupServerWindow((ProfileItem)obj).ShowDialog<bool>(_window);
case EViewAction.ShareServer:
if (obj is null)
{
return false;
}
await ShareServer((string)obj);
break;
case EViewAction.SubEditWindow:
if (obj is null)
{
return false;
}
return await new SubEditWindow((SubItem)obj).ShowDialog<bool>(_window);
case EViewAction.DispatcherRefreshServersBiz:
@@ -215,14 +236,17 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
{
var source = e.Source as Border;
if (source?.Name == "HeaderBackground")
{
return;
}
if (_config.UiItem.DoubleClick2Activate)
{
ViewModel?.SetDefaultServer();
}
else
{
ViewModel?.EditServerAsync(EConfigType.Custom);
ViewModel?.EditServerAsync();
}
}
@@ -263,7 +287,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
break;
case Key.D:
ViewModel?.EditServerAsync(EConfigType.Custom);
ViewModel?.EditServerAsync();
break;
case Key.F:

View File

@@ -83,7 +83,10 @@ public partial class RoutingRuleSettingWindow : WindowBase<RoutingRuleSettingVie
case EViewAction.RoutingRuleDetailsWindow:
if (obj is null)
{
return false;
}
return await new RoutingRuleDetailsWindow((RulesItem)obj).ShowDialog<bool>(this);
case EViewAction.ImportRulesFromFile:

View File

@@ -60,7 +60,10 @@ public partial class RoutingSettingWindow : WindowBase<RoutingSettingViewModel>
case EViewAction.RoutingRuleSettingWindow:
if (obj is null)
{
return false;
}
return await new RoutingRuleSettingWindow((RoutingItem)obj).ShowDialog<bool>(this);
}
return await Task.FromResult(true);

View File

@@ -56,7 +56,10 @@ public partial class StatusBarView : ReactiveUserControl<StatusBarViewModel>
case EViewAction.SetClipboardData:
if (obj is null)
{
return false;
}
await AvaUtils.SetClipboardData(this, (string)obj);
break;

View File

@@ -49,14 +49,20 @@ public partial class SubSettingWindow : WindowBase<SubSettingViewModel>
case EViewAction.SubEditWindow:
if (obj is null)
{
return false;
}
var window = new SubEditWindow((SubItem)obj);
await window.ShowDialog(this);
break;
case EViewAction.ShareSub:
if (obj is null)
{
return false;
}
await ShareSub((string)obj);
break;
}

View File

@@ -76,7 +76,7 @@ internal static class WindowsUtils
{
using var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
var obj = key?.GetValue("AppsUseLightTheme");
int.TryParse(obj?.ToString(), out var value);
var value = obj?.ToString().ToInt();
return value == 0;
}

View File

@@ -6,14 +6,14 @@ public class DelayColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int.TryParse(value.ToString(), out var delay);
var delay = value.ToString().ToInt();
if (delay <= 0)
return new SolidColorBrush(Colors.Red);
if (delay <= 500)
return new SolidColorBrush(Colors.Green);
else
return new SolidColorBrush(Colors.IndianRed);
return delay switch
{
<= 0 => new SolidColorBrush(Colors.Red),
<= 500 => new SolidColorBrush(Colors.Green),
_ => new SolidColorBrush(Colors.IndianRed)
};
}
public object? ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

View File

@@ -138,7 +138,9 @@ public class ThemeSettingViewModel : MyReactiveObject
{
double size = (long)CurrentFontSize;
if (size < Global.MinFontSize)
{
return;
}
Application.Current.Resources["StdFontSize"] = size;
Application.Current.Resources["StdFontSize1"] = size + 1;

View File

@@ -39,6 +39,7 @@
IsCancel="true"
Style="{StaticResource DefButton}" />
</StackPanel>
<Grid DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -48,8 +49,6 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
@@ -59,6 +58,8 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
@@ -132,6 +133,63 @@
</Grid>
</Grid>
</Grid>
<TabControl DockPanel.Dock="Top">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<Grid Margin="{StaticResource Margin8}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.menuSubscription}" />
<ComboBox
x:Name="cmbSubChildItems"
Grid.Row="0"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
DisplayMemberPath="Remarks"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="0"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.LvFilter}" />
<TextBox
x:Name="txtFilter"
Grid.Row="1"
Grid.Column="1"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
AcceptsReturn="True"
Style="{StaticResource DefTextBox}" />
</Grid>
</TabItem>
</TabControl>
<TabControl>
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<DataGrid

View File

@@ -41,6 +41,9 @@ public partial class AddGroupServerWindow
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.ChildItemsObs, v => v.lstChild.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables);
@@ -75,7 +78,10 @@ public partial class AddGroupServerWindow
private void AddGroupServerWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (!lstChild.IsKeyboardFocusWithin)
{
return;
}
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
if (e.Key == Key.A)

View File

@@ -928,6 +928,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
@@ -995,6 +996,63 @@
Width="200"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbCertPinning}" />
<StackPanel
Grid.Row="5"
Grid.Column="1"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
x:Name="labCertPinning"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" />
<materialDesign:PopupBox
HorizontalAlignment="Left"
VerticalAlignment="Center"
StaysOpen="True"
Style="{StaticResource MaterialDesignToolForegroundPopupBox}">
<StackPanel>
<TextBlock
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbCertPinningTips}"
TextWrapping="Wrap" />
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<Button
x:Name="btnFetchCert"
Width="100"
Margin="{StaticResource MarginLeftRight4}"
Content="{x:Static resx:ResUI.TbFetchCert}"
Style="{StaticResource DefButton}" />
<Button
x:Name="btnFetchCertChain"
Width="100"
Margin="{StaticResource MarginLeftRight4}"
Content="{x:Static resx:ResUI.TbFetchCertChain}"
Style="{StaticResource DefButton}" />
</StackPanel>
<TextBox
x:Name="txtCert"
Width="400"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
AcceptsReturn="True"
MinLines="6"
Style="{StaticResource MyOutlinedTextBox}"
TextWrapping="Wrap" />
</StackPanel>
</materialDesign:PopupBox>
</StackPanel>
</Grid>
<Grid
x:Name="gridRealityMore"

View File

@@ -180,6 +180,8 @@ public partial class AddServerWindow
this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.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.Cert, v => v.txtCert.Text).DisposeWith(disposables);
//reality
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);
@@ -188,6 +190,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.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);
});

View File

@@ -21,9 +21,9 @@ public partial class MainWindow
App.Current.SessionEnding += Current_SessionEnding;
Closing += MainWindow_Closing;
PreviewKeyDown += MainWindow_PreviewKeyDown;
menuSettingsSetUWP.Click += menuSettingsSetUWP_Click;
menuPromotion.Click += menuPromotion_Click;
menuClose.Click += menuClose_Click;
menuSettingsSetUWP.Click += MenuSettingsSetUWP_Click;
menuPromotion.Click += MenuPromotion_Click;
menuClose.Click += MenuClose_Click;
menuCheckUpdate.Click += MenuCheckUpdate_Click;
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
@@ -178,6 +178,7 @@ public partial class MainWindow
private async Task DelegateSnackMsg(string content)
{
MainSnackbar.MessageQueue?.Enqueue(content);
await Task.CompletedTask;
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
@@ -186,17 +187,26 @@ public partial class MainWindow
{
case EViewAction.AddServerWindow:
if (obj is null)
{
return false;
}
return new AddServerWindow((ProfileItem)obj).ShowDialog() ?? false;
case EViewAction.AddServer2Window:
if (obj is null)
{
return false;
}
return new AddServer2Window((ProfileItem)obj).ShowDialog() ?? false;
case EViewAction.AddGroupServerWindow:
if (obj is null)
{
return false;
}
return new AddGroupServerWindow((ProfileItem)obj).ShowDialog() ?? false;
case EViewAction.DNSSettingWindow:
@@ -297,18 +307,18 @@ public partial class MainWindow
}
}
private void menuClose_Click(object sender, RoutedEventArgs e)
private void MenuClose_Click(object sender, RoutedEventArgs e)
{
StorageUI();
ShowHideWindow(false);
}
private void menuPromotion_Click(object sender, RoutedEventArgs e)
private void MenuPromotion_Click(object sender, RoutedEventArgs e)
{
ProcUtils.ProcessStart($"{Utils.Base64Decode(Global.PromotionUrl)}?t={DateTime.Now.Ticks}");
}
private void menuSettingsSetUWP_Click(object sender, RoutedEventArgs e)
private void MenuSettingsSetUWP_Click(object sender, RoutedEventArgs e)
{
ProcUtils.ProcessStart(Utils.GetBinPath("EnableLoopback.exe"));
}
@@ -429,8 +439,8 @@ public partial class MainWindow
{
var coreInfo = CoreInfoManager.Instance.GetCoreInfo();
foreach (var it in coreInfo
.Where(t => t.CoreType != ECoreType.v2fly
&& t.CoreType != ECoreType.hysteria))
.Where(t => t.CoreType is not ECoreType.v2fly
and not ECoreType.hysteria))
{
var item = new MenuItem()
{

View File

@@ -30,7 +30,10 @@ public partial class MsgView
{
case EViewAction.DispatcherShowMsg:
if (obj is null)
{
return false;
}
Application.Current?.Dispatcher.Invoke(() =>
{
ShowMsg(obj);

View File

@@ -109,7 +109,10 @@ public partial class ProfilesView
{
case EViewAction.SetClipboardData:
if (obj is null)
{
return false;
}
WindowsUtils.SetClipboardData((string)obj);
break;
@@ -126,7 +129,10 @@ public partial class ProfilesView
case EViewAction.SaveFileDialog:
if (obj is null)
{
return false;
}
if (UI.SaveFileDialog(out var fileName, "Config|*.json") != true)
{
return false;
@@ -136,28 +142,43 @@ public partial class ProfilesView
case EViewAction.AddServerWindow:
if (obj is null)
{
return false;
}
return new AddServerWindow((ProfileItem)obj).ShowDialog() ?? false;
case EViewAction.AddServer2Window:
if (obj is null)
{
return false;
}
return new AddServer2Window((ProfileItem)obj).ShowDialog() ?? false;
case EViewAction.AddGroupServerWindow:
if (obj is null)
{
return false;
}
return new AddGroupServerWindow((ProfileItem)obj).ShowDialog() ?? false;
case EViewAction.ShareServer:
if (obj is null)
{
return false;
}
ShareServer((string)obj);
break;
case EViewAction.SubEditWindow:
if (obj is null)
{
return false;
}
return new SubEditWindow((SubItem)obj).ShowDialog() ?? false;
case EViewAction.DispatcherRefreshServersBiz:
@@ -209,7 +230,7 @@ public partial class ProfilesView
}
else
{
ViewModel?.EditServerAsync(EConfigType.Custom);
ViewModel?.EditServerAsync();
}
}
@@ -245,7 +266,7 @@ public partial class ProfilesView
break;
case Key.D:
ViewModel?.EditServerAsync(EConfigType.Custom);
ViewModel?.EditServerAsync();
break;
case Key.F:
@@ -424,15 +445,22 @@ public partial class ProfilesView
{
// Get the dragged Item
if (sender is not DataGrid listView)
{
return;
}
var listViewItem = FindAncestor<DataGridRow>((DependencyObject)e.OriginalSource);
if (listViewItem == null)
{
return; // Abort
// Find the data behind the ListViewItem
}
// Find the data behind the ListViewItem
var item = (ProfileItemModel)listView.ItemContainerGenerator.ItemFromContainer(listViewItem);
if (item == null)
{
return; // Abort
// Initialize the drag & drop operation
}
// Initialize the drag & drop operation
startIndex = lstProfiles.SelectedIndex;
DataObject dragData = new(formatData, item);
DragDrop.DoDragDrop(listViewItem, dragData, DragDropEffects.Copy | DragDropEffects.Move);
@@ -453,7 +481,10 @@ public partial class ProfilesView
{
// Get the drop Item destination
if (sender is not DataGrid listView)
{
return;
}
var listViewItem = FindAncestor<DataGridRow>((DependencyObject)e.OriginalSource);
if (listViewItem == null)
{
@@ -464,7 +495,9 @@ public partial class ProfilesView
// Find the data behind the Item
var item = (ProfileItemModel)listView.ItemContainerGenerator.ItemFromContainer(listViewItem);
if (item == null)
{
return;
}
// Move item into observable collection
// (this will be automatically reflected to lstView.ItemsSource)
e.Effects = DragDropEffects.Move;

View File

@@ -79,7 +79,10 @@ public partial class RoutingRuleSettingWindow
case EViewAction.RoutingRuleDetailsWindow:
if (obj is null)
{
return false;
}
return new RoutingRuleDetailsWindow((RulesItem)obj).ShowDialog() ?? false;
case EViewAction.ImportRulesFromFile:
@@ -93,7 +96,10 @@ public partial class RoutingRuleSettingWindow
case EViewAction.SetClipboardData:
if (obj is null)
{
return false;
}
WindowsUtils.SetClipboardData((string)obj);
break;
@@ -117,7 +123,10 @@ public partial class RoutingRuleSettingWindow
private void RoutingRuleSettingWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (!lstRules.IsKeyboardFocusWithin)
{
return;
}
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
if (e.Key == Key.A)

View File

@@ -57,7 +57,10 @@ public partial class RoutingSettingWindow
case EViewAction.RoutingRuleSettingWindow:
if (obj is null)
{
return false;
}
return new RoutingRuleSettingWindow((RoutingItem)obj).ShowDialog() ?? false;
}
return await Task.FromResult(true);

View File

@@ -80,7 +80,10 @@ public partial class StatusBarView
case EViewAction.SetClipboardData:
if (obj is null)
{
return false;
}
WindowsUtils.SetClipboardData((string)obj);
break;
}

View File

@@ -46,12 +46,18 @@ public partial class SubSettingWindow
case EViewAction.SubEditWindow:
if (obj is null)
{
return false;
}
return new SubEditWindow((SubItem)obj).ShowDialog() ?? false;
case EViewAction.ShareSub:
if (obj is null)
{
return false;
}
ShareSub((string)obj);
break;
}