Imports System.Globalization
Imports System.IO.Compression
Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices
Imports System.Text.RegularExpressions
Imports System.Xaml
Imports System.Threading.Tasks
Imports Microsoft.Win32
Imports Newtonsoft.Json
Imports PCL.Core.App
Imports PCL.Core.Logging
Imports PCL.Core.Utils
Imports System.Windows
Imports PCL.Core.Utils.Codecs
Imports PCL.Core.Utils.OS

Public Module ModBase

#Region "声明"

    '下列版本信息由更新器自动修改
    Public Const VersionBaseName As String = "2.13.2" '不含分支前缀的显示用版本名
    Public Const VersionStandardCode As String = "2.13.2." & VersionBranchCode
    Public Const UpstreamVersion As String = "2.10.5" '上游版本
    Public ReadOnly CommitHash As String = If(EnvironmentInterop.GetSecret("GITHUB_SHA", False), "native") 'Commit Hash
    Public ReadOnly CommitHashShort As String = If(CommitHash = "native", "native", CommitHash.Substring(0, 7)) 'Commit Hash，取前 7 位
    Public Const VersionCode As Integer = 412 '内部版本号
    '自动生成的版本信息
#If DEBUG Then
    Public Const VersionBranchName As String = "Debug"
    Public Const VersionBranchCode As String = "100"
#ElseIf DEBUGCI Then
    Public Const VersionBranchName As String = "CI"
    Public Const VersionBranchCode As String = "50"
#Else
    Public Const VersionBranchName As String = "Publish"
    Public Const VersionBranchCode As String = "0"
#End If

    ''' <summary>
    ''' 主窗口句柄。
    ''' </summary>
    Public FrmHandle As IntPtr
    '龙猫味石山小记: 用最不靠谱的实现写出能跑的代码 (AppDomain.CurrentDomain.SetupInformation.ApplicationBase 获取到的是当前工作目录而不是可执行文件所在目录)
    ''' <summary>
    ''' 程序可执行文件所在目录，以“\”结尾。
    ''' </summary>
    Public ReadOnly ExePath As String = If(Basics.ExecutableDirectory.EndsWith("\"), Basics.ExecutableDirectory, Basics.ExecutableDirectory & "\")
    ''' <summary>
    ''' 程序可执行文件完整路径。
    ''' </summary>
    Public ReadOnly ExePathWithName As String = Basics.ExecutablePath
    ''' <summary>
    ''' 程序内嵌图片文件夹路径，以“/”结尾。
    ''' </summary>
    Public ReadOnly PathImage As String = "pack://application:,,,/Plain Craft Launcher 2;component/Images/"
    ''' <summary>
    ''' 当前程序的语言。
    ''' </summary>
    Public Lang As String = "zh_CN"
    ''' <summary>
    ''' 设置对象。
    ''' </summary>
    Public Setup As New ModSetup
    ''' <summary>
    ''' 程序的打开计时。
    ''' </summary>
    Public ApplicationStartTick As Long = TimeUtils.GetTimeTick()
    ''' <summary>
    ''' 程序打开时的时间。
    ''' </summary>
    Public ApplicationOpenTime As Date = Date.Now
    ''' <summary>
    ''' 识别码。
    ''' </summary>
    Public UniqueAddress As String = SecretGetUniqueAddress()
    ''' <summary>
    ''' 程序是否已结束。
    ''' </summary>
    Public IsProgramEnded As Boolean = False
    ''' <summary>
    ''' 是否为 32 位系统。
    ''' </summary>
    Public Is32BitSystem As Boolean = Not Environment.Is64BitOperatingSystem
    ''' <summary>
    ''' 是否为 ARM64 架构。
    ''' </summary>
    Public IsArm64System As Boolean = Runtime.InteropServices.RuntimeInformation.OSArchitecture = Runtime.InteropServices.Architecture.Arm64
    ''' <summary>
    ''' 是否使用 GBK 编码。
    ''' </summary>
    Public IsGBKEncoding As Boolean = Encoding.Default.CodePage = 936
    ''' <summary>
    ''' 系统盘盘符，以 \ 结尾。例如 “C:\”。
    ''' </summary>
    Public OsDrive As String = Environment.GetLogicalDrives().Where(Function(p) Directory.Exists(p)).First.ToUpper.First & ":\" '#3799
    ''' <summary>
    ''' 程序的缓存文件夹路径，以 \ 结尾。
    ''' </summary>
    Public PathTemp As String = If(Setup.Get("SystemSystemCache") = "", IO.Path.GetTempPath() & "PCL\", Setup.Get("SystemSystemCache")).ToString.Replace("/", "\").TrimEnd("\") & "\"
    ''' <summary>
    ''' AppData 中的 PCL 文件夹路径，以 \ 结尾。
    ''' </summary>
    Public PathAppdata As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\PCL\"
    ''' <summary>
    ''' AppData 中的 PCLCE 配置文件夹路径，以 \ 结尾。
    ''' </summary>
    Public PathAppdataConfig As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & If(VersionBranchName = "Debug", "\.pclcedebug\", "\.pclce\")

    Public PathHelpFolder As String = PathTemp & "CE\Help\"

#End Region

#Region "矢量图标"

    Public Class Logo
        ''' <summary>
        ''' 图标按钮，心（空心），1.1x
        ''' </summary>
        Public Const IconButtonLikeLine As String = "M512 896a42.666667 42.666667 0 0 1-30.293333-12.373333l-331.52-331.946667a224.426667 224.426667 0 0 1 0-315.733333 223.573333 223.573333 0 0 1 315.733333 0L512 282.026667l46.08-46.08a223.573333 223.573333 0 0 1 315.733333 0 224.426667 224.426667 0 0 1 0 315.733333l-331.52 331.946667A42.666667 42.666667 0 0 1 512 896zM308.053333 256a136.533333 136.533333 0 0 0-97.28 40.106667 138.24 138.24 0 0 0 0 194.986666L512 792.746667l301.226667-301.653334a138.24 138.24 0 0 0 0-194.986666 141.653333 141.653333 0 0 0-194.56 0l-76.373334 76.8a42.666667 42.666667 0 0 1-60.586666 0L405.333333 296.106667A136.533333 136.533333 0 0 0 308.053333 256z"
        ''' <summary>
        ''' 图标按钮，心（实心），1.1x
        ''' </summary>
        Public Const IconButtonLikeFill As String = "M700.856 155.543c-74.769 0-144.295 72.696-190.046 127.26-45.737-54.576-115.247-127.26-190.056-127.26-134.79 0-244.443 105.78-244.443 235.799 0 77.57 39.278 131.988 70.845 175.713C238.908 694.053 469.62 852.094 479.39 858.757c9.41 6.414 20.424 9.629 31.401 9.629 11.006 0 21.998-3.215 31.398-9.63 9.782-6.662 240.514-164.703 332.238-291.701 31.587-43.724 70.874-98.143 70.874-175.713-0.001-130.02-109.656-235.8-244.445-235.8z m0 0"
        ''' <summary>
        ''' 图标按钮，垃圾桶，1.1x
        ''' </summary>
        Public Const IconButtonDelete As String = "M520.192 0C408.43 0 317.44 82.87 313.563 186.734H52.736c-29.038 0-52.663 21.943-52.663 49.079s23.625 49.152 52.663 49.152h58.075v550.473c0 103.35 75.118 187.757 167.717 187.757h472.43c92.599 0 167.716-83.894 167.716-187.757V285.477h52.59c29.038 0 52.59-21.943 52.663-49.08-0.073-27.135-23.625-49.151-52.663-49.151H726.235C723.237 83.017 631.955 0 520.192 0zM404.846 177.957c3.803-50.03 50.176-89.015 107.447-89.015 57.197 0 103.57 38.985 106.788 89.015H404.92zM284.379 933.669c-33.353 0-69.997-39.351-69.997-95.525v-549.01H833.39v549.522c0 56.247-36.645 95.525-69.998 95.525H284.379v-0.512z M357.23 800.695a48.274 48.274 0 0 0 47.616-49.006V471.7a48.274 48.274 0 0 0-47.543-49.08 48.274 48.274 0 0 0-47.69 49.006V751.69c0 27.282 20.846 49.006 47.617 49.006z m166.62 0a48.274 48.274 0 0 0 47.688-49.006V471.7a48.274 48.274 0 0 0-47.689-49.08 48.274 48.274 0 0 0-47.543 49.006V751.69c0 27.282 21.431 49.006 47.543 49.006z m142.92 0a48.274 48.274 0 0 0 47.543-49.006V471.7a48.274 48.274 0 0 0-47.543-49.08 48.274 48.274 0 0 0-47.616 49.006V751.69c0 27.282 20.773 49.006 47.543 49.006z"
        ''' <summary>
        ''' 图标按钮，禁止，1x
        ''' </summary>
        Public Const IconButtonStop As String = "M508 990.4c-261.6 0-474.4-212-474.4-474.4S246.4 41.6 508 41.6s474.4 212 474.4 474.4S769.6 990.4 508 990.4zM508 136.8c-209.6 0-379.2 169.6-379.2 379.2 0 209.6 169.6 379.2 379.2 379.2s379.2-169.6 379.2-379.2C887.2 306.4 717.6 136.8 508 136.8zM697.6 563.2 318.4 563.2c-26.4 0-47.2-21.6-47.2-47.2 0-26.4 21.6-47.2 47.2-47.2l379.2 0c26.4 0 47.2 21.6 47.2 47.2C744.8 542.4 724 563.2 697.6 563.2z"
        ''' <summary>
        ''' 图标按钮，勾选，1x
        ''' </summary>
        Public Const IconButtonCheck As String = "M512 0a512 512 0 1 0 512 512A512 512 0 0 0 512 0z m0 921.6a409.6 409.6 0 1 1 409.6-409.6 409.6 409.6 0 0 1-409.6 409.6z M716.8 339.968l-256 253.44L328.192 460.8A51.2 51.2 0 0 0 256 532.992l168.448 168.96a51.2 51.2 0 0 0 72.704 0l289.28-289.792A51.2 51.2 0 0 0 716.8 339.968z"
        ''' <summary>
        ''' 图标按钮，笔，1x
        ''' </summary>
        Public Const IconButtonEdit As String = "M732.64 64.32C688.576 21.216 613.696 21.216 569.6 64.32L120.128 499.52c-17.6 12.896-26.432 30.144-30.848 51.68L32 870.048c0 25.856 8.8 56 26.432 73.248 17.632 17.216 17.632 48.704 88.64 48.704h13.248l326.08-56c22.016-4.32 39.68-12.928 52.864-30.176l449.472-435.2c22.048-21.536 35.264-47.36 35.264-77.536 0-30.176-13.216-56-35.264-77.568l-256.096-251.2zM139.712 903.776l56-326.912 311.04-295.136 267.104 269.44-310.976 295.168-323.168 57.44zM844.576 467.84l-273.984-260.672 61.856-59.84c8.832-8.512 26.528-8.512 39.776 0l234.24 226.496c4.384 4.288 8.832 12.8 8.832 17.088s-4.416 8.544-8.864 12.8l-61.856 64.128z"
        ''' <summary>
        ''' 图标按钮，齿轮，1.1x
        ''' </summary>
        Public Const IconButtonSetup As String = "M651.946667 1001.813333c-22.186667 0-42.666667-10.24-61.44-27.306666-23.893333-23.893333-49.493333-35.84-75.093334-35.84-29.013333 0-56.32 11.946667-73.386666 30.72v3.413333c-17.066667 17.066667-42.666667 27.306667-66.56 27.306667h-6.826667c-6.826667 0-11.946667-1.706667-15.36-1.706667l-6.826667-1.706667c-64.853333-20.48-121.173333-54.613333-168.96-98.986666-29.013333-23.893333-37.546667-63.146667-25.6-95.573334 8.533333-23.893333 5.12-51.2-10.24-75.093333-15.36-27.306667-34.133333-40.96-59.733333-47.786667h-1.706667l-5.12-1.706666c-35.84-8.533333-61.44-34.133333-66.56-69.973334C1.706667 575.146667 0 537.6 0 512c0-32.426667 3.413333-63.146667 8.533333-93.866667v-6.826666l3.413334-8.533334c10.24-23.893333 23.893333-40.96 44.373333-51.2 5.12-3.413333 11.946667-6.826667 20.48-8.533333 27.306667-8.533333 51.2-25.6 63.146667-44.373333 13.653333-23.893333 17.066667-52.906667 10.24-81.92-11.946667-34.133333 0-71.68 30.72-93.866667 44.373333-37.546667 97.28-68.266667 158.72-93.866667l3.413333-1.706666c44.373333-13.653333 75.093333 3.413333 92.16 20.48 23.893333 23.893333 49.493333 35.84 75.093333 35.84 30.72 0 56.32-10.24 71.68-30.72l3.413334-3.413334c27.306667-27.306667 63.146667-35.84 93.866666-22.186666 63.146667 22.186667 117.76 54.613333 165.546667 97.28 29.013333 23.893333 37.546667 63.146667 25.6 95.573333-8.533333 23.893333-5.12 51.2 10.24 75.093333 15.36 27.306667 34.133333 40.96 59.733333 47.786667h1.706667l5.12 1.706667c35.84 8.533333 61.44 34.133333 66.56 71.68 6.826667 30.72 10.24 63.146667 11.946667 93.866666v3.413334c0 32.426667-3.413333 63.146667-8.533334 93.866666v6.826667l-3.413333 8.533333c-10.24 23.893333-23.893333 40.96-44.373333 51.2-5.12 3.413333-11.946667 6.826667-20.48 8.533334-27.306667 8.533333-51.2 25.6-63.146667 46.08-13.653333 23.893333-17.066667 52.906667-10.24 81.92 11.946667 35.84-1.706667 75.093333-30.72 95.573333-44.373333 35.84-95.573333 66.56-157.013333 92.16-15.36 3.413333-27.306667 3.413333-35.84 3.413333z m3.413333-83.626666z m1.706667 0zM517.12 853.333333c47.786667 0 93.866667 20.48 134.826667 59.733334 1.706667 1.706667 3.413333 1.706667 3.413333 3.413333 52.906667-22.186667 97.28-49.493333 136.533333-80.213333l1.706667-1.706667v-3.413333c-13.653333-52.906667-8.533333-104.106667 17.066667-148.48 23.893333-39.253333 64.853333-69.973333 114.346666-85.333334 1.706667 0 3.413333-1.706667 6.826667-6.826666 5.12-25.6 8.533333-51.2 8.533333-78.506667-1.706667-29.013333-3.413333-56.32-10.24-81.92v-5.12h-1.706666c-51.2-11.946667-90.453333-39.253333-119.466667-87.04-27.306667-44.373333-34.133333-100.693333-17.066667-148.48l-1.706666-1.706667h-3.413334c-39.253333-35.84-85.333333-63.146667-136.533333-80.213333H648.533333s-1.706667 1.706667-3.413333 1.706667c-32.426667 39.253333-80.213333 59.733333-136.533333 59.733333-47.786667 0-93.866667-20.48-134.826667-59.733333l-1.706667-1.706667h-1.706666c-54.613333 22.186667-98.986667 49.493333-136.533334 80.213333l-1.706666 1.706667v3.413333c13.653333 52.906667 8.533333 104.106667-17.066667 148.48-23.893333 39.253333-64.853333 69.973333-114.346667 85.333334-1.706667 0-3.413333 1.706667-6.826666 6.826666-6.826667 25.6-8.533333 51.2-8.533334 78.506667 0 30.72 3.413333 58.026667 6.826667 76.8l1.706667 5.12h1.706666c51.2 11.946667 90.453333 39.253333 119.466667 87.04 27.306667 44.373333 34.133333 100.693333 17.066667 148.48l1.706666 1.706667 1.706667 1.706666c37.546667 35.84 83.626667 63.146667 134.826667 80.213334 1.706667 0 3.413333 0 3.413333 1.706666h1.706667s1.706667 0 5.12-1.706666c34.133333-37.546667 81.92-59.733333 136.533333-59.733334z m-6.826667-146.773333c-110.933333 0-199.68-85.333333-199.68-196.266667 0-109.226667 87.04-196.266667 199.68-196.266666s199.68 85.333333 199.68 196.266666c-1.706667 109.226667-88.746667 196.266667-199.68 196.266667z m0-307.2c-63.146667 0-114.346667 49.493333-114.346666 110.933333 0 63.146667 49.493333 110.933333 114.346666 110.933334 30.72 0 59.733333-11.946667 80.213334-32.426667 20.48-20.48 32.426667-49.493333 32.426666-78.506667 0-63.146667-49.493333-110.933333-112.64-110.933333z"
        ''' <summary>
        ''' 图标按钮，重置，0.9x
        ''' </summary>
        Public Const IconButtonReset As String = "M667.6817627 313.65283203l-45.28564454 55.76660156L858.06933594 391.27124023 787.61950684 165.93066406l-56.01379395 69.01611328A354.47387695 354.47387695 0 0 0 520.89892578 165.93066406C324.87536621 165.93066406 165.93066406 324.43041992 165.93066406 519.91015625c0 195.52917481 158.94470215 353.97949219 354.96826172 353.97949219a355.06713867 355.06713867 0 0 0 331.73217774-227.66418458 50.52612305 50.52612305 0 0 0-29.21813966-65.25878905 50.77331543 50.77331543 0 0 0-65.50598144 29.16870117A253.61938477 253.61938477 0 0 1 520.94836426 772.78796387c-140.05920411 0-253.61938477-113.21411133-253.61938477-252.87780762 0-139.61425781 113.56018067-252.82836914 253.61938477-252.82836914 53.59130859 0 104.46350098 16.61132813 146.73339843 46.57104492"
        ''' <summary>
        ''' 图标按钮，刷新，0.85x
        ''' </summary>
        Public Const IconButtonRefresh As String = "M875.52 148.48C783.36 56.32 655.36 0 512 0 291.84 0 107.52 138.24 30.72 332.8l122.88 46.08C204.8 230.4 348.16 128 512 128c107.52 0 199.68 40.96 271.36 112.64L640 384h384V0L875.52 148.48zM512 896c-107.52 0-199.68-40.96-271.36-112.64L384 640H0v384l148.48-148.48C240.64 967.68 368.64 1024 512 1024c220.16 0 404.48-138.24 481.28-332.8L870.4 645.12C819.2 793.6 675.84 896 512 896z"
        ''' <summary>
        ''' 图标按钮，软盘，1x
        ''' </summary>
        Public Const IconButtonSave As String = "M819.392 0L1024 202.752v652.16a168.96 168.96 0 0 1-168.832 168.768h-104.192a47.296 47.296 0 0 1-10.752 0H283.776a47.232 47.232 0 0 1-10.752 0H168.832A168.96 168.96 0 0 1 0 854.912V168.768A168.96 168.96 0 0 1 168.832 0h650.56z m110.208 854.912V242.112l-149.12-147.776H168.896c-41.088 0-74.432 33.408-74.432 74.432v686.144c0 41.024 33.344 74.432 74.432 74.432h62.4v-190.528c0-33.408 27.136-60.544 60.544-60.544h440.448c33.408 0 60.544 27.136 60.544 60.544v190.528h62.4c41.088 0 74.432-33.408 74.432-74.432z m-604.032 74.432h372.864v-156.736H325.568v156.736z m403.52-596.48a47.168 47.168 0 1 1 0 94.336H287.872a47.168 47.168 0 1 1 0-94.336h441.216z m0-153.728a47.168 47.168 0 1 1 0 94.4H287.872a47.168 47.168 0 1 1 0-94.4h441.216z"
        ''' <summary>
        ''' 图标按钮，信息，1.05x
        ''' </summary>
        Public Const IconButtonInfo As String = "M512 917.333333c223.861333 0 405.333333-181.472 405.333333-405.333333S735.861333 106.666667 512 106.666667 106.666667 288.138667 106.666667 512s181.472 405.333333 405.333333 405.333333z m0 106.666667C229.226667 1024 0 794.773333 0 512S229.226667 0 512 0s512 229.226667 512 512-229.226667 512-512 512z m-32-597.333333h64a21.333333 21.333333 0 0 1 21.333333 21.333333v320a21.333333 21.333333 0 0 1-21.333333 21.333333h-64a21.333333 21.333333 0 0 1-21.333333-21.333333V448a21.333333 21.333333 0 0 1 21.333333-21.333333z m0-192h64a21.333333 21.333333 0 0 1 21.333333 21.333333v64a21.333333 21.333333 0 0 1-21.333333 21.333333h-64a21.333333 21.333333 0 0 1-21.333333-21.333333v-64a21.333333 21.333333 0 0 1 21.333333-21.333333z"
        ''' <summary>
        ''' 图标按钮，列表，1x
        ''' </summary>
        Public Const IconButtonList As String = "M384 128h640v128H384zM160 192m-96 0a96 96 0 1 0 192 0 96 96 0 1 0-192 0ZM384 448h640v128H384zM160 512m-96 0a96 96 0 1 0 192 0 96 96 0 1 0-192 0ZM384 768h640v128H384zM160 832m-96 0a96 96 0 1 0 192 0 96 96 0 1 0-192 0Z"
        ''' <summary>
        ''' 图标按钮，文件夹，1.15x
        ''' </summary>
        Public Const IconButtonOpen As String = "M889.018182 418.909091H884.363636V316.509091a93.090909 93.090909 0 0 0-99.607272-89.832727h-302.545455l-93.090909-76.334546A46.545455 46.545455 0 0 0 358.865455 139.636364H146.152727A93.090909 93.090909 0 0 0 46.545455 229.469091V837.818182a46.545455 46.545455 0 0 0 46.545454 46.545454 46.545455 46.545455 0 0 0 16.756364-3.258181 109.381818 109.381818 0 0 0 25.134545 3.258181h586.472727a85.178182 85.178182 0 0 0 87.04-63.301818l163.374546-302.545454a46.545455 46.545455 0 0 0 5.585454-21.876364A82.385455 82.385455 0 0 0 889.018182 418.909091z m-744.727273-186.181818h198.283636l93.09091 76.334545a46.545455 46.545455 0 0 0 29.323636 10.705455h319.301818a12.101818 12.101818 0 0 1 6.516364 0V418.909091H302.545455a85.178182 85.178182 0 0 0-87.04 63.301818L139.636364 622.778182V232.727273a19.549091 19.549091 0 0 1 6.516363 0z m578.094546 552.029091a27.461818 27.461818 0 0 0-2.792728 6.516363H154.530909l147.083636-272.290909a27.461818 27.461818 0 0 0 2.792728-6.981818h565.061818z"
        ''' <summary>
        ''' 图标按钮，名片，1.1x
        ''' </summary>
        Public Const IconButtonCard As String = "M834.5 684.1c-31.2-70.4-98.9-120.9-179.1-127.3 63.5-8.5 112.6-63 112.6-128.8 0-71.8-58.2-130-130-130s-130 58.2-130 130c0 65.9 49 120.3 112.6 128.8-80.2 6.4-148 57-179.1 127.3-8.7 19.7 6 42 27.6 42 12.1 0 22.7-7.5 27.7-18.5 24.3-53.9 78.5-91.5 141.3-91.5s117 37.6 141.3 91.5c5 11.1 15.6 18.5 27.7 18.5 21.4 0 36.1-22.3 27.4-42zM567.9 427.9c0-38.6 31.4-70 70-70s70 31.4 70 70-31.4 70-70 70-70-31.4-70-70zM460.3 347.9H216.9c-16.6 0-30 13.4-30 30s13.4 30 30 30h243.3c16.6 0 30-13.4 30-30 0.1-16.5-13.4-30-29.9-30zM367.4 459.6H216.9c-16.6 0-30 13.4-30 30s13.4 30 30 30h150.4c16.6 0 30-13.4 30-30 0.1-16.6-13.4-30-29.9-30zM297.4 571.2H217c-16.6 0-30 13.4-30 30s13.4 30 30 30h80.4c16.6 0 30-13.4 30-30 0-16.5-13.5-30-30-30zM900 236v552H124V236h776m0-60H124c-33.1 0-60 26.9-60 60v552c0 33.1 26.9 60 60 60h776c33.1 0 60-26.9 60-60V236c0-33.1-26.9-60-60-60z"
        ''' <summary>
        ''' 图标按钮，×，0.85x
        ''' </summary>
        Public Const IconButtonCross As String = "F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z"
        ''' <summary>
        ''' 图标按钮，验证，1.1x
        ''' </summary>
        Public Const IconButtonAuth As String = "M511.488256 95.184408c35.310345 22.516742 95.184408 55.78011 167.34033 84.437781 75.738131 29.681159 148.405797 40.93953 191.392304 45.033483v353.615193c0 73.691154-50.662669 164.781609-136.123938 244.101949C649.65917 901.181409 558.568716 942.12094 512 942.12094c-46.568716 0-137.65917-40.93953-222.096952-119.748126C204.441779 742.54073 153.77911 651.450275 153.77911 577.247376v-353.103448c42.474763-4.093953 116.165917-15.352324 191.904048-45.545227 75.226387-30.192904 133.565217-63.456272 165.805098-83.414293M512 0c-4.093953 0-8.187906 1.535232-11.258371 3.582209l-14.84058 10.234882c-1.023488 0.511744-67.550225 47.592204-170.410794 88.531735-100.813593 39.916042-198.556722 41.963018-199.58021 41.963018l-25.075462 0.511744c-10.746627 0.511744-18.934533 8.187906-18.934533 18.422789v414.000999c0 216.97951 286.064968 446.24088 440.09995 446.24088s440.09995-229.261369 440.09995-445.729136V163.758121c0-10.234883-8.69965-18.422789-18.934533-18.422789l-24.563718-0.511744c-1.023488 0-98.766617-2.046977-199.58021-41.963018-103.372314-40.93953-170.410795-88.01999-170.922538-88.531734L523.258371 3.582209c-3.070465-2.558721-7.164418-3.582209-11.258371-3.582209z M743.308346 410.930535l-260.477761 260.477761c-15.864068 15.864068-41.963018 15.864068-57.827087 0l-144.823588-144.823588c-15.864068-15.864068-15.864068-41.963018 0-57.827087 8.187906-8.187906 18.422789-11.770115 29.169415-11.770115 10.234883 0 20.981509 4.093953 29.169416 11.770115l115.654173 115.654173L685.993003 352.591704c15.864068-15.864068 41.963018-15.864068 57.827087 0 15.352324 16.375812 15.352324 42.474763-0.511744 58.338831z"
        ''' <summary>
        ''' 图标按钮，第三方
        ''' </summary>
        Public Const IconButtonThirdparty As String = "M865.004 167.069c-10.794-9.687-24.91-15.085-39.579-15.085-1.383 0-2.629 0-4.013 0.139-0.831 0.139-10.102 0.692-24.771 0.692-24.218 0-71.408-1.522-116.107-12.178-57.708-13.7-124.411-77.083-143.785-89.675-9.687-6.227-21.034-9.41-32.244-9.41-11.21 0-22.42 3.182-32.244 9.41-2.353 1.522-72.1 73.484-140.324 89.675-44.699 10.655-92.72 12.178-116.938 12.178-14.53 0-23.941-0.554-24.771-0.692-1.246-0.139-2.629-0.139-3.875-0.139-14.67 0-28.924 5.396-39.717 15.085-11.763 10.655-18.405 25.325-18.405 40.825v140.048c0 517.846 351.089 584.411 366.034 587.040 3.46 0.554 6.782 0.831 10.241 0.831 3.46 0 6.918-0.276 10.241-0.831 14.946-2.629 368.663-69.33 368.663-587.040v-139.911c0.139-15.5-6.642-30.446-18.405-40.962v0zM825.425 348.080c0 476.883-320.783 531.961-320.783 531.961s-318.291-55.078-318.291-531.961v-140.048c0 0 10.933 0.831 28.785 0.831 30.446 0 81.648-2.214 130.777-13.839 80.403-19.098 158.731-97.564 158.731-97.564s81.787 78.466 162.19 97.564c49.129 11.625 99.501 13.839 129.946 13.839 17.714 0 28.785-0.831 28.785-0.831l-0.139 140.048zM463.405 491.173z M349.925 603.958l66.841-15.085c10.102 54.663 40.962 81.925 92.72 81.925 57.43-1.383 87.045-29.476 88.429-84.14 0-50.373-35.289-75.421-105.728-75.421-17.299 0-30.998 0-40.962 0v-51.757c10.102 0 20.757 0 32.382 0 66.149 0 99.916-25.187 101.3-75.421-1.383-45.945-26.571-69.747-75.421-71.132-48.85 0-77.635 26.571-86.215 79.85l-64.766-15.085c18.683-76.252 70.438-114.308 155.27-114.308 87.738 2.906 134.373 40.962 140.187 114.308-1.383 53.279-30.998 87.738-88.429 103.514 63.244 13.008 97.009 49.542 101.3 110.019-4.29 81.925-56.878 124.411-157.486 127.316-87.461 1.246-140.739-36.811-159.422-114.585z"
        ''' <summary>
        ''' 图标按钮，用户，0.95x
        ''' </summary>
        Public Const IconButtonUser As String = "M660.338 528.065c63.61-46.825 105.131-121.964 105.131-206.83 0-141.7-115.29-256.987-256.997-256.987-141.706 0-256.998 115.288-256.998 256.987 0 85.901 42.52 161.887 107.456 208.562-152.1 59.92-260.185 207.961-260.185 381.077 0 21.276 17.253 38.53 38.53 38.53 21.278 0 38.53-17.254 38.53-38.53 0-183.426 149.232-332.671 332.667-332.671 1.589 0 3.113-0.207 4.694-0.244 0.8 0.056 1.553 0.244 2.362 0.244 183.434 0 332.664 149.245 332.664 332.671 0 21.276 17.255 38.53 38.533 38.53 21.277 0 38.53-17.254 38.53-38.53 0-174.885-110.354-324.13-264.917-382.809z m-331.803-206.83c0-99.22 80.72-179.927 179.935-179.927s179.937 80.708 179.937 179.927c0 99.203-80.721 179.91-179.937 179.91s-179.935-80.708-179.935-179.91z"
        ''' <summary>
        ''' 图标按钮，盾牌，1x
        ''' </summary>
        Public Const IconButtonShield As String = "M511.488256 95.184408c35.310345 22.516742 95.184408 55.78011 167.34033 84.437781 75.738131 29.681159 148.405797 40.93953 191.392304 45.033483v353.615193c0 73.691154-50.662669 164.781609-136.123938 244.101949C649.65917 901.181409 558.568716 942.12094 512 942.12094c-46.568716 0-137.65917-40.93953-222.096952-119.748126C204.441779 742.54073 153.77911 651.450275 153.77911 577.247376v-353.103448c42.474763-4.093953 116.165917-15.352324 191.904048-45.545227 75.226387-30.192904 133.565217-63.456272 165.805098-83.414293M512 0c-4.093953 0-8.187906 1.535232-11.258371 3.582209l-14.84058 10.234882c-1.023488 0.511744-67.550225 47.592204-170.410794 88.531735-100.813593 39.916042-198.556722 41.963018-199.58021 41.963018l-25.075462 0.511744c-10.746627 0.511744-18.934533 8.187906-18.934533 18.422789v414.000999c0 216.97951 286.064968 446.24088 440.09995 446.24088s440.09995-229.261369 440.09995-445.729136V163.758121c0-10.234883-8.69965-18.422789-18.934533-18.422789l-24.563718-0.511744c-1.023488 0-98.766617-2.046977-199.58021-41.963018-103.372314-40.93953-170.410795-88.01999-170.922538-88.531734L523.258371 3.582209c-3.070465-2.558721-7.164418-3.582209-11.258371-3.582209z M743.308346 410.930535l-260.477761 260.477761c-15.864068 15.864068-41.963018 15.864068-57.827087 0l-144.823588-144.823588c-15.864068-15.864068-15.864068-41.963018 0-57.827087 8.187906-8.187906 18.422789-11.770115 29.169415-11.770115 10.234883 0 20.981509 4.093953 29.169416 11.770115l115.654173 115.654173L685.993003 352.591704c15.864068-15.864068 41.963018-15.864068 57.827087 0 15.352324 16.375812 15.352324 42.474763-0.511744 58.338831z"
        ''' <summary>
        ''' 图标按钮，离线，0.85x
        ''' </summary>
        Public Const IconButtonOffline As String = "M533.293176 788.841412a60.235294 60.235294 0 1 1 85.202824 85.202823l-42.616471 42.586353c-129.355294 129.385412-339.124706 129.385412-468.510117 0-129.385412-129.385412-129.385412-339.124706 0-468.510117l42.586353-42.616471a60.235294 60.235294 0 1 1 85.202823 85.202824l-42.61647 42.586352a210.823529 210.823529 0 1 0 298.164706 298.164706l42.586352-42.61647z m255.548236-255.548236l42.61647-42.586352a210.823529 210.823529 0 1 0-298.164706-298.164706l-42.586352 42.61647a60.235294 60.235294 0 1 1-85.202824-85.202823l42.616471-42.586353c129.355294-129.385412 339.124706-129.385412 468.510117 0 129.385412 129.385412 129.385412 339.124706 0 468.510117l-42.586353 42.616471a60.235294 60.235294 0 1 1-85.202823-85.202824zM192.542118 192.542118a60.235294 60.235294 0 0 1 85.202823 0l553.712941 553.712941a60.235294 60.235294 0 0 1-85.202823 85.202823L192.542118 277.744941a60.235294 60.235294 0 0 1 0-85.202823z"
        ''' <summary>
        ''' 图标，服务端，1x
        ''' </summary>
        Public Const IconButtonServer As String = "M224 160a64 64 0 0 0-64 64v576a64 64 0 0 0 64 64h576a64 64 0 0 0 64-64V224a64 64 0 0 0-64-64H224z m0 384h576v256H224v-256z m192 96v64h320v-64H416z m-128 0v64h64v-64H288zM224 224h576v256H224V224z m192 96v64h320v-64H416z m-128 0v64h64v-64H288z"
        ''' <summary>
        ''' 图标按钮，复制
        ''' </summary>
        Public Const IconButtonCopy As String = "M394.666667 106.666667h448a74.666667 74.666667 0 0 1 74.666666 74.666666v448a74.666667 74.666667 0 0 1-74.666666 74.666667H394.666667a74.666667 74.666667 0 0 1-74.666667-74.666667V181.333333a74.666667 74.666667 0 0 1 74.666667-74.666666z m0 64a10.666667 10.666667 0 0 0-10.666667 10.666666v448a10.666667 10.666667 0 0 0 10.666667 10.666667h448a10.666667 10.666667 0 0 0 10.666666-10.666667V181.333333a10.666667 10.666667 0 0 0-10.666666-10.666666H394.666667z m245.333333 597.333333a32 32 0 0 1 64 0v74.666667a74.666667 74.666667 0 0 1-74.666667 74.666666H181.333333a74.666667 74.666667 0 0 1-74.666666-74.666666V394.666667a74.666667 74.666667 0 0 1 74.666666-74.666667h74.666667a32 32 0 0 1 0 64h-74.666667a10.666667 10.666667 0 0 0-10.666666 10.666667v448a10.666667 10.666667 0 0 0 10.666666 10.666666h448a10.666667 10.666667 0 0 0 10.666667-10.666666v-74.666667z"
        ''' <summary>
        ''' 图标按钮，外链
        ''' </summary>
        Public Const IconButtonlink As String = "M433.230769 74.830769a43.323077 43.323077 0 0 1 0 86.646154l-236.307692 0.157539a35.446154 35.446154 0 0 0-35.446154 35.446153v630.153847a35.446154 35.446154 0 0 0 35.446154 35.446153h630.153846a35.446154 35.446154 0 0 0 35.446154-35.446153V590.769231a43.323077 43.323077 0 1 1 86.646154 0v236.425846a122.092308 122.092308 0 0 1-122.092308 122.092308H196.923077a122.092308 122.092308 0 0 1-122.092308-122.092308v-630.153846a122.092308 122.092308 0 0 1 122.092308-122.092308z m452.923077 0a63.015385 63.015385 0 0 1 63.015385 63.015385V354.461538a43.323077 43.323077 0 0 1-43.323077 43.323077l-4.726154-0.236307A43.323077 43.323077 0 0 1 862.523077 354.461538l-0.039385-131.702153-287.074461 287.15323-90.072616 90.072616a43.323077 43.323077 0 1 1-61.243077-61.243077l90.033231-90.072616 287.113846-287.192615H669.538462a43.323077 43.323077 0 0 1-43.08677-38.596923L626.215385 118.153846A43.323077 43.323077 0 0 1 669.538462 74.830769z"
        ''' <summary>
        ''' 图标，音符，1x
        ''' </summary>
        Public Const IconMusic As String = "M348.293565 716.53287V254.797913c0-41.672348 28.004174-78.358261 68.919652-90.37913L815.994435 40.826435c62.775652-18.610087 125.907478 26.579478 125.907478 89.933913v539.158261c8.013913 42.25113-8.94887 89.177043-47.014956 127.109565a232.848696 232.848696 0 0 1-170.785392 65.758609c-61.885217-2.938435-111.081739-33.435826-129.113043-80.050087-18.031304-46.614261-2.137043-102.177391 41.672348-145.853218a232.848696 232.848696 0 0 1 170.785391-65.80313c21.014261 1.024 40.514783 5.164522 57.878261 12.065391V233.338435c0-12.109913-10.551652-20.034783-20.569044-20.034783a24.620522 24.620522 0 0 0-5.787826 0.934957L439.785739 338.18713a19.545043 19.545043 0 0 0-14.825739 19.144348v438.984348H423.846957c11.53113 43.987478-5.164522 94.208-45.412174 134.322087a232.848696 232.848696 0 0 1-170.785392 65.758609c-61.885217-2.938435-111.081739-33.435826-129.113043-80.050087-18.031304-46.614261-2.137043-102.177391 41.672348-145.853218a232.848696 232.848696 0 0 1 170.785391-65.80313c20.791652 1.024 40.069565 5.075478 57.299478 11.842783z"
        ''' <summary>
        ''' 图标，播放，0.8x
        ''' </summary>
        Public Const IconPlay As String = "M803.904 463.936a55.168 55.168 0 0 1 0 96.128l-463.616 264.448C302.848 845.888 256 819.136 256 776.448V247.616c0-42.752 46.848-69.44 84.288-48.064l463.616 264.384z"
        ''' <summary>
        ''' 图标，创建，0.9x
        ''' </summary>
        Public Const IconButtonCreate As String = "M103.331925 384.978025l25.805736 0L129.137661 161.847132c0-18.313088 14.905478-33.718963 33.718963-33.718963l0.969071 0 253.006318 0c10.82044 0 20.218484 4.797259 26.500561 12.257162l117.579929 126.753869 297.819966 0c18.297738 0 33.736359 15.179724 33.736359 33.977859l0 0.952698 0 82.909292 25.547863 0c18.538215 0 34.187637 15.179724 34.187637 33.977859 0 2.163269-0.469698 3.617387-0.469698 5.539156l-54.437843 432.971086c-1.210571 10.382465-7.007601 19.056008-14.968923 24.352641-6.249331 5.765307-14.680351 9.624195-23.595394 9.624195l-0.969071 0-694.906773 0c-9.155521 0-17.344017-3.858888-23.626094-9.155521-8.67252-5.765307-14.453177-14.939247-15.389502-25.758664L69.597613 423.040922c-2.165316-18.313088 10.868535-35.414581 29.665647-38.062897L103.331925 384.978025 103.331925 384.978025zM196.576609 384.978025 196.576609 384.978025l627.938546 0 0-49.625234L546.461371 335.352791l0 0c-9.400091 0-18.329461-4.117784-25.048489-11.110035L402.363486 196.067514 196.576609 196.067514 196.576609 384.978025 196.576609 384.978025zM879.469767 452.916347 879.469767 452.916347l-20.267603 0-0.469698 0-0.969071 0-694.906773 0-0.984421 0-20.218484 0 45.781696 366.728382 646.218888 0L879.469767 452.916347 879.469767 452.916347z"
        ''' <summary>
        ''' 图标，分享，1x
        ''' </summary>
        Public Const IconButtonShare As String = "M768.704 703.616c-35.648 0-67.904 14.72-91.136 38.304l-309.152-171.712c9.056-17.568 14.688-37.184 14.688-58.272 0-12.576-2.368-24.48-5.76-35.936l304.608-189.152c22.688 20.416 52.384 33.184 85.216 33.184 70.592 0 128-57.408 128-128s-57.408-128-128-128-128 57.408-128 128c0 14.56 2.976 28.352 7.456 41.408l-301.824 187.392c-23.136-22.784-54.784-36.928-89.728-36.928-70.592 0-128 57.408-128 128 0 70.592 57.408 128 128 128 25.664 0 49.504-7.744 69.568-20.8l321.216 178.4c-3.04 10.944-5.184 22.208-5.184 34.08 0 70.592 57.408 128 128 128s128-57.408 128-128S839.328 703.616 768.704 703.616zM767.2 128.032c35.296 0 64 28.704 64 64s-28.704 64-64 64-64-28.704-64-64S731.904 128.032 767.2 128.032zM191.136 511.936c0-35.296 28.704-64 64-64s64 28.704 64 64c0 35.296-28.704 64-64 64S191.136 547.232 191.136 511.936zM768.704 895.616c-35.296 0-64-28.704-64-64s28.704-64 64-64 64 28.704 64 64S804 895.616 768.704 895.616z"
        ''' <summary>
        ''' 图标，添加，1x
        ''' </summary>
        Public Const IconButtonAdd As String = "M512.277 954.412c-118.89 0-230.659-46.078-314.73-129.73S67.12 629.666 67.12 511.222s46.327-229.744 130.398-313.427 195.82-129.73 314.73-129.73 230.659 46.078 314.72 129.73S957.397 392.81 957.397 511.183 911.078 740.96 826.97 824.642s-195.8 129.77-314.692 129.77z m0-822.784c-101.972 0-197.809 39.494-269.865 111.222s-111.7 166.997-111.7 268.373 39.653 196.695 111.67 268.335S410.246 890.78 512.248 890.78s197.809-39.484 269.865-111.222 111.7-166.998 111.67-268.374c-0.03-101.375-39.654-196.665-111.67-268.303S614.22 131.628 512.277 131.628z m222.585 347.8H544.073V288.64c-0.76-17.561-15.613-31.18-33.173-30.419-16.495 0.714-29.704 13.924-30.419 30.419v190.787H289.703c-17.56 0.761-31.179 15.614-30.419 33.174 0.715 16.494 13.924 29.703 30.42 30.418H480.48v190.788c0.761 17.56 15.614 31.179 33.174 30.419 16.494-0.715 29.703-13.925 30.418-30.42V543.02h190.788c17.56 0.762 32.413-12.857 33.173-30.418 0.762-17.561-12.858-32.414-30.419-33.174a31.683 31.683 0 0 0-2.753 0z"
        ''' <summary>
        ''' 图标，开始游戏，1x
        ''' </summary>
        Public Const IconPlayGame As String = "M213.333333 65.386667a85.333333 85.333333 0 0 1 43.904 12.16L859.370667 438.826667a85.333333 85.333333 0 0 1 0 146.346666L257.237333 946.453333A85.333333 85.333333 0 0 1 128 873.28V150.72a85.333333 85.333333 0 0 1 85.333333-85.333333z m0 64a21.333333 21.333333 0 0 0-21.184 18.837333L192 150.72v722.56a21.333333 21.333333 0 0 0 30.101333 19.456l2.197334-1.152L826.453333 530.282667a21.333333 21.333333 0 0 0 2.048-35.178667l-2.048-1.386667L224.298667 132.416A21.333333 21.333333 0 0 0 213.333333 129.386667z"
        Public Const IconButtonEnable As String = "M512 0a512 512 0 1 0 512 512A512 512 0 0 0 512 0z m0 921.6a409.6 409.6 0 1 1 409.6-409.6 409.6 409.6 0 0 1-409.6 409.6z M716.8 339.968l-256 253.44L328.192 460.8A51.2 51.2 0 0 0 256 532.992l168.448 168.96a51.2 51.2 0 0 0 72.704 0l289.28-289.792A51.2 51.2 0 0 0 716.8 339.968z"
        Public Const IconButtonDisable As String = "M508 990.4c-261.6 0-474.4-212-474.4-474.4S246.4 41.6 508 41.6s474.4 212 474.4 474.4S769.6 990.4 508 990.4zM508 136.8c-209.6 0-379.2 169.6-379.2 379.2 0 209.6 169.6 379.2 379.2 379.2s379.2-169.6 379.2-379.2C887.2 306.4 717.6 136.8 508 136.8zM697.6 563.2 318.4 563.2c-26.4 0-47.2-21.6-47.2-47.2 0-26.4 21.6-47.2 47.2-47.2l379.2 0c26.4 0 47.2 21.6 47.2 47.2C744.8 542.4 724 563.2 697.6 563.2z"
    End Class

#End Region

#Region "自定义类"

    ''' <summary>
    ''' 支持小数与常见类型隐式转换的颜色。
    ''' </summary>
    Public Class MyColor

        Public A As Double = 255
        Public R As Double = 0
        Public G As Double = 0
        Public B As Double = 0

        '类型转换
        Public Shared Widening Operator CType(str As String) As MyColor
            Return New MyColor(str)
        End Operator
        Public Shared Widening Operator CType(col As Color) As MyColor
            Return New MyColor(col)
        End Operator
        Public Shared Widening Operator CType(conv As MyColor) As Color
            Return Color.FromArgb(MathByte(conv.A), MathByte(conv.R), MathByte(conv.G), MathByte(conv.B))
        End Operator
        Public Shared Widening Operator CType(conv As MyColor) As System.Drawing.Color
            Return System.Drawing.Color.FromArgb(MathByte(conv.A), MathByte(conv.R), MathByte(conv.G), MathByte(conv.B))
        End Operator
        Public Shared Widening Operator CType(bru As SolidColorBrush) As MyColor
            Return New MyColor(bru.Color)
        End Operator
        Public Shared Widening Operator CType(conv As MyColor) As SolidColorBrush
            Return New SolidColorBrush(Color.FromArgb(MathByte(conv.A), MathByte(conv.R), MathByte(conv.G), MathByte(conv.B)))
        End Operator
        Public Shared Widening Operator CType(bru As Brush) As MyColor
            Return New MyColor(bru)
        End Operator
        Public Shared Widening Operator CType(conv As MyColor) As Brush
            Return New SolidColorBrush(Color.FromArgb(MathByte(conv.A), MathByte(conv.R), MathByte(conv.G), MathByte(conv.B)))
        End Operator

        '颜色运算
        Public Shared Operator +(a As MyColor, b As MyColor) As MyColor
            Return New MyColor With {.A = a.A + b.A, .B = a.B + b.B, .G = a.G + b.G, .R = a.R + b.R}
        End Operator
        Public Shared Operator -(a As MyColor, b As MyColor) As MyColor
            Return New MyColor With {.A = a.A - b.A, .B = a.B - b.B, .G = a.G - b.G, .R = a.R - b.R}
        End Operator
        Public Shared Operator *(a As MyColor, b As Double) As MyColor
            Return New MyColor With {.A = a.A * b, .B = a.B * b, .G = a.G * b, .R = a.R * b}
        End Operator
        Public Shared Operator /(a As MyColor, b As Double) As MyColor
            Return New MyColor With {.A = a.A / b, .B = a.B / b, .G = a.G / b, .R = a.R / b}
        End Operator
        Public Shared Operator =(a As MyColor, b As MyColor) As Boolean
            If IsNothing(a) AndAlso IsNothing(b) Then Return True
            If IsNothing(a) OrElse IsNothing(b) Then Return False
            Return a.A = b.A AndAlso a.R = b.R AndAlso a.G = b.G AndAlso a.B = b.B
        End Operator
        Public Shared Operator <>(a As MyColor, b As MyColor) As Boolean
            If IsNothing(a) AndAlso IsNothing(b) Then Return False
            If IsNothing(a) OrElse IsNothing(b) Then Return True
            Return Not (a.A = b.A AndAlso a.R = b.R AndAlso a.G = b.G AndAlso a.B = b.B)
        End Operator

        '构造函数
        Public Sub New()
        End Sub
        Public Sub New(col As Color)
            Me.A = col.A
            Me.R = col.R
            Me.G = col.G
            Me.B = col.B
        End Sub
        Public Sub New(HexString As String)
            Dim StringColor As Media.Color = ColorConverter.ConvertFromString(HexString)
            A = StringColor.A
            R = StringColor.R
            G = StringColor.G
            B = StringColor.B
        End Sub
        Public Sub New(newA As Double, col As MyColor)
            Me.A = newA
            Me.R = col.R
            Me.G = col.G
            Me.B = col.B
        End Sub
        Public Sub New(newR As Double, newG As Double, newB As Double)
            Me.A = 255
            Me.R = newR
            Me.G = newG
            Me.B = newB
        End Sub
        Public Sub New(newA As Double, newR As Double, newG As Double, newB As Double)
            Me.A = newA
            Me.R = newR
            Me.G = newG
            Me.B = newB
        End Sub
        Public Sub New(brush As Brush)
            Dim Color As Color = CType(brush, SolidColorBrush).Color
            A = Color.A
            R = Color.R
            G = Color.G
            B = Color.B
        End Sub
        Public Sub New(brush As SolidColorBrush)
            Dim Color As Color = brush.Color
            A = Color.A
            R = Color.R
            G = Color.G
            B = Color.B
        End Sub
        Public Sub New(obj As Object)
            If obj Is Nothing Then
                A = 255 : R = 255 : G = 255 : B = 255
            Else
                If TypeOf obj Is SolidColorBrush Then
                    '避免反复获取 Color 对象造成性能下降
                    Dim Color As Color = CType(obj, SolidColorBrush).Color
                    A = Color.A
                    R = Color.R
                    G = Color.G
                    B = Color.B
                Else
                    A = obj.A
                    R = obj.R
                    G = obj.G
                    B = obj.B
                End If
            End If
        End Sub

        'HSL
        Public Function Hue(v1 As Double, v2 As Double, vH As Double) As Double
            If vH < 0 Then vH += 1
            If vH > 1 Then vH -= 1
            If vH < 0.16667 Then Return v1 + (v2 - v1) * 6 * vH
            If vH < 0.5 Then Return v2
            If vH < 0.66667 Then Return v1 + (v2 - v1) * (4 - vH * 6)
            Return v1
        End Function
        Public Function FromHSL(sH As Double, sS As Double, sL As Double) As MyColor
            If sS = 0 Then
                R = sL * 2.55
                G = R
                B = R
            Else
                Dim H = sH / 360
                Dim S = sS / 100
                Dim L = sL / 100
                S = If(L < 0.5, S * L + L, S * (1.0 - L) + L)
                L = 2 * L - S
                R = 255 * Hue(L, S, H + 1 / 3)
                G = 255 * Hue(L, S, H)
                B = 255 * Hue(L, S, H - 1 / 3)
            End If
            A = 255
            Return Me
        End Function
        Public Function FromHSL2(sH As Double, sS As Double, sL As Double) As MyColor
            If sS = 0 Then
                R = sL * 2.55 : G = R : B = R
            Else
                '初始化
                sH = (sH + 3600000) Mod 360
                Dim cent As Double() = {
                    +0.1, -0.06, -0.3, '0, 30, 60
                    -0.19, -0.15, -0.24, '90, 120, 150
                    -0.32, -0.09, +0.18, '180, 210, 240
                    +0.05, -0.12, -0.02, '270, 300, 330
                    +0.1, -0.06} '最后两位与前两位一致，加是变亮，减是变暗
                '计算色调对应的亮度片区
                Dim center As Double = sH / 30.0
                Dim intCenter As Integer = Math.Floor(center) '亮度片区编号
                center = 50 - (
                     (1 - center + intCenter) * cent(intCenter) + (center - intCenter) * cent(intCenter + 1)
                    ) * sS
                'center = 50 + (cent(intCenter) + (center - intCenter) * (cent(intCenter + 1) - cent(intCenter))) * sS
                sL = If(sL < center, sL / center, 1 + (sL - center) / (100 - center)) * 50
                FromHSL(sH, sS, sL)
            End If
            A = 255
            Return Me
        End Function

        Public Function Alpha(sA As Double) As MyColor
            A = sA
            Return Me
        End Function

        Public Overrides Function ToString() As String
            Return "(" & A & "," & R & "," & G & "," & B & ")"
        End Function
        Public Overrides Function Equals(obj As Object) As Boolean
            Return Me = obj
        End Function

    End Class

    ''' <summary>
    ''' 支持负数与浮点数的矩形。
    ''' </summary>
    Public Class MyRect

        '属性
        Public Property Width As Double = 0
        Public Property Height As Double = 0
        Public Property Left As Double = 0
        Public Property Top As Double = 0

        '构造函数
        Public Sub New()
        End Sub
        Public Sub New(left As Double, top As Double, width As Double, height As Double)
            Me.Left = left
            Me.Top = top
            Me.Width = width
            Me.Height = height
        End Sub

    End Class

    ''' <summary>
    ''' 模块加载状态枚举。
    ''' </summary>
    Public Enum LoadState
        Waiting
        Loading
        Finished
        Failed
        Aborted
    End Enum

    ''' <summary>
    ''' 执行返回值。
    ''' </summary>
    Public Enum ProcessReturnValues
        ''' <summary>
        ''' 执行成功，或进程被中断。
        ''' </summary>
        Aborted = -1
        ''' <summary>
        ''' 执行成功。
        ''' </summary>
        Success = 0
        ''' <summary>
        ''' 执行失败。
        ''' </summary>
        Fail = 1
        ''' <summary>
        ''' 执行时出现未经处理的异常。
        ''' </summary>
        Exception = 2
        ''' <summary>
        ''' 执行超时。
        ''' </summary>
        Timeout = 3
        ''' <summary>
        ''' 取消执行。可能是由于不满足执行的前置条件。
        ''' </summary>
        Cancel = 4
        ''' <summary>
        ''' 任务成功完成。
        ''' </summary>
        TaskDone = 5
    End Enum

    ''' <summary>
    ''' 可以使用 Equals 和等号的 List。
    ''' </summary>
    Public Class EqualableList(Of T)
        Inherits List(Of T)
        Public Overrides Function Equals(obj As Object) As Boolean
            If TryCast(obj, List(Of T)) Is Nothing Then
                '类型不同
                Return False
            Else
                '类型相同
                Dim objList As List(Of T) = obj
                If objList.Count <> Count Then Return False
                For i = 0 To objList.Count - 1
                    If Not objList(i).Equals(Me(i)) Then Return False
                Next
                Return True
            End If
        End Function
        Public Shared Operator =(left As EqualableList(Of T), right As EqualableList(Of T)) As Boolean
            Return EqualityComparer(Of EqualableList(Of T)).Default.Equals(left, right)
        End Operator
        Public Shared Operator <>(left As EqualableList(Of T), right As EqualableList(Of T)) As Boolean
            Return Not left = right
        End Operator
    End Class

#End Region

#Region "数学"

    ''' <summary>
    ''' 2~65 进制的转换。
    ''' </summary>
    Public Function RadixConvert(Input As String, FromRadix As Integer, ToRadix As Integer) As String
        Const Digits As String = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/+="
        '零与负数的处理
        If String.IsNullOrEmpty(Input) Then Return "0"
        Dim IsNegative As Boolean = Input.StartsWithF("-")
        If IsNegative Then Input = Input.TrimStart("-")
        '转换为十进制
        Dim RealNum As Long = 0, Scale As Long = 1
        For Each Digit In Input.Reverse.Select(Function(l) Digits.IndexOfF(l))
            RealNum += Digit * Scale
            Scale *= FromRadix
        Next
        '转换为指定进制
        Dim Result = ""
        While RealNum > 0
            Dim NewNum As Integer = RealNum Mod ToRadix
            RealNum = (RealNum - NewNum) / ToRadix
            Result = Digits(NewNum) & Result
        End While
        '负数的结束处理与返回
        Return If(IsNegative, "-", "") & Result
    End Function

    ''' <summary>
    ''' 计算二阶贝塞尔曲线。
    ''' </summary>
    Public Function MathBezier(x As Double, x1 As Double, y1 As Double, x2 As Double, y2 As Double, Optional acc As Double = 0.01) As Double
        If x <= 0 OrElse Double.IsNaN(x) Then Return 0
        If x >= 1 Then Return 1
        Dim a, b
        a = x
        Do
            b = 3 * a * ((0.33333333 + x1 - x2) * a * a + (x2 - 2 * x1) * a + x1)
            a += (x - b) * 0.5
        Loop Until Math.Abs(b - x) < acc '精度
        Return 3 * a * ((0.33333333 + y1 - y2) * a * a + (y2 - 2 * y1) * a + y1)
    End Function

    ''' <summary>
    ''' 将一个数字限制为 0~255 的 Byte 值。
    ''' </summary>
    Public Function MathByte(d As Double) As Byte
        If d < 0 Then d = 0
        If d > 255 Then d = 255
        Return Math.Round(d)
    End Function

    ''' <summary>
    ''' 提供 MyColor 类型支持的 Math.Round。
    ''' </summary>
    Public Function MathRound(col As MyColor, Optional w As Integer = 0) As MyColor
        Return New MyColor With {.A = Math.Round(col.A, w), .R = Math.Round(col.R, w), .G = Math.Round(col.G, w), .B = Math.Round(col.B, w)}
    End Function

    ''' <summary>
    ''' 获取两数间的百分比。小数点精确到 6 位。
    ''' </summary>
    ''' <returns></returns>
    Public Function MathPercent(ValueA As Double, ValueB As Double, Percent As Double) As Double
        Return Math.Round(ValueA * (1 - Percent) + ValueB * Percent, 6) '解决 Double 计算错误
    End Function
    ''' <summary>
    ''' 获取两颜色间的百分比，根据 RGB 计算。小数点精确到 6 位。
    ''' </summary>
    Public Function MathPercent(ValueA As MyColor, ValueB As MyColor, Percent As Double) As MyColor
        Return MathRound(ValueA * (1 - Percent) + ValueB * Percent, 6) '解决Double计算错误
    End Function

    ''' <summary>
    ''' 将数值限定在某个范围内。
    ''' </summary>
    Public Function MathClamp(value As Double, min As Double, max As Double) As Double
        Return Math.Max(min, Math.Min(max, value))
    End Function

    ''' <summary>
    ''' 符号函数。
    ''' </summary>
    Public Function MathSgn(Value As Double) As Integer
        If Value = 0 Then
            Return 0
        ElseIf Value > 0 Then
            Return 1
        Else
            Return -1
        End If
    End Function

#End Region

#Region "文件"

    '=============================
    '  注册表
    '=============================

    ''' <summary>
    ''' 重命名一个注册表子键。不可用于包含子键的子键。
    ''' </summary>
    Public Sub RenameReg(parentKey As Microsoft.Win32.RegistryKey, subKeyName As String, newSubKeyName As String)
        If parentKey.GetSubKeyNames().Contains(newSubKeyName) Then parentKey.DeleteSubKeyTree(newSubKeyName, False)
        Dim SourceKey As Microsoft.Win32.RegistryKey = parentKey.OpenSubKey(subKeyName)
        If IsNothing(SourceKey) Then Exit Sub '没有目标项
        Dim NewKey As Microsoft.Win32.RegistryKey = parentKey.CreateSubKey(newSubKeyName)
        If SourceKey.GetSubKeyNames().Length > 0 Then Throw New NotSupportedException("不支持对包含子键的子键进行重命名：" & SourceKey.GetSubKeyNames()(0) & "。")
        For Each valueName As String In SourceKey.GetValueNames()
            Dim objValue As Object = SourceKey.GetValue(valueName)
            Dim valKind As Microsoft.Win32.RegistryValueKind = SourceKey.GetValueKind(valueName)
            NewKey.SetValue(valueName, objValue, valKind)
        Next
        parentKey.DeleteSubKeyTree(subKeyName, False)
    End Sub
    ''' <summary>
    ''' 读取注册表，默认为程序所属。
    ''' </summary>
    Public Function ReadReg(Key As String, Optional DefaultValue As String = "", Optional Path As String = "") As String
        Try
            Dim parentKey As RegistryKey, softKey As RegistryKey
            parentKey = Registry.CurrentUser
            softKey = parentKey.OpenSubKey("Software\" & If(Path = "", RegFolder, Path), True)
            If softKey Is Nothing Then
                ReadReg = DefaultValue '不存在则返回默认值
            Else
                Dim readValue As New Text.StringBuilder
                readValue.AppendLine(softKey.GetValue(Key))
                Dim value = readValue.ToString.Replace(vbCrLf, "") '去除莫名的回车
                Return If(value = "", DefaultValue, value) '错误则返回默认值
            End If
        Catch ex As Exception
            Log(ex, "读取注册表出错：" & Key, LogLevel.Hint)
            Return DefaultValue
        End Try
    End Function
    ''' <summary>
    ''' 写入注册表，默认为程序所属。
    ''' </summary>
    Public Sub WriteReg(Key As String, Value As String, Optional ShowException As Boolean = False, Optional Path As String = "", Optional ThrowException As Boolean = False)
        Try
            Dim parentKey As RegistryKey, softKey As RegistryKey
            parentKey = Registry.CurrentUser
            softKey = parentKey.OpenSubKey("Software\" & If(Path = "", RegFolder, Path), True)
            If softKey Is Nothing Then softKey = parentKey.CreateSubKey("Software\" & If(Path = "", RegFolder, Path)) '如果不存在就创建  
            softKey.SetValue(Key, Value)
        Catch ex As Exception
            Log(ex, "写入注册表出错：" & Key, If(ThrowException, LogLevel.Hint, LogLevel.Developer))
            If ThrowException Then Throw
        End Try
    End Sub
    ''' <summary>
    ''' 是否存在某个注册表键。
    ''' </summary>
    Public Function HasReg(Key As String) As Boolean
        Return ReadReg(Key, Nothing) IsNot Nothing
    End Function
    ''' <summary>
    ''' 删除注册表键。
    ''' </summary>
    Public Sub DeleteReg(Key As String, Optional ThrowException As Boolean = False)
        Try
            Dim SubKey As Microsoft.Win32.RegistryKey = Registry.CurrentUser.OpenSubKey("Software\" & RegFolder, True)
            SubKey?.DeleteValue(Key)
        Catch ex As Exception
            Log(ex, "删除注册表出错：" & Key, If(ThrowException, LogLevel.Hint, LogLevel.Developer))
            If ThrowException Then Throw
        End Try
    End Sub

    '=============================
    '  ini
    '=============================

    Private ReadOnly IniCache As New SafeDictionary(Of String, SafeDictionary(Of String, String))
    ''' <summary>
    ''' 清除某 ini 文件的运行时缓存。
    ''' </summary>
    ''' <param name="FileName">文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。</param>
    Public Sub IniClearCache(FileName As String)
        If Not FileName.Contains(":\") Then FileName = $"{ExePath}PCL\{FileName}.ini"
        If IniCache.ContainsKey(FileName) Then IniCache.Remove(FileName)
    End Sub
    ''' <summary>
    ''' 获取 ini 文件缓存。如果没有，则新读取 ini 文件内容。
    ''' 在文件不存在或读取失败时返回 Nothing。
    ''' </summary>
    ''' <param name="FileName">文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。</param>
    Private Function IniGetContent(FileName As String) As SafeDictionary(Of String, String)
        Try
            '还原文件路径
            If Not FileName.Contains(":\") Then FileName = $"{ExePath}PCL\{FileName}.ini"
            '检索缓存
            If IniCache.ContainsKey(FileName) Then Return IniCache(FileName)
            '读取文件
            If Not File.Exists(FileName) Then Return Nothing
            Dim Ini As New SafeDictionary(Of String, String)
            For Each Line In ReadFile(FileName).Split(vbCrLf.ToArray(), StringSplitOptions.RemoveEmptyEntries)
                Dim Index As Integer = Line.IndexOfF(":")
                If Index > 0 Then Ini(Line.Substring(0, Index)) = Line.Substring(Index + 1) '可能会有重复键，见 #3616
            Next
            IniCache(FileName) = Ini
            Return Ini
        Catch ex As Exception
            Log(ex, $"生成 ini 文件缓存失败（{FileName}）", LogLevel.Hint)
            Return Nothing
        End Try
    End Function
    ''' <summary>
    ''' 读取 ini 文件。这可能会使用到缓存。
    ''' </summary>
    ''' <param name="FileName">文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。</param>
    ''' <param name="Key">键。</param>
    ''' <param name="DefaultValue">没有找到键时返回的默认值。</param>
    Public Function ReadIni(FileName As String, Key As String, Optional DefaultValue As String = "") As String
        Dim Content = IniGetContent(FileName)
        If Content Is Nothing OrElse Not Content.ContainsKey(Key) Then Return DefaultValue
        Return Content(Key)
    End Function
    ''' <summary>
    ''' 判断 ini 文件中是否包含某个键。这可能会使用到缓存。
    ''' </summary>
    Public Function HasIniKey(FileName As String, Key As String) As Boolean
        Dim Content = IniGetContent(FileName)
        Return Content IsNot Nothing AndAlso Content.ContainsKey(Key)
    End Function
    ''' <summary>
    ''' 从 ini 文件中移除某个键。这会更新缓存。
    ''' </summary>
    Public Sub DeleteIniKey(FileName As String, Key As String)
        WriteIni(FileName, Key, Nothing)
    End Sub
    ''' <summary>
    ''' 写入 ini 文件，这会更新缓存。
    ''' 若 Value 为 Nothing，则删除该键。
    ''' </summary>
    ''' <param name="FileName">文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。</param>
    ''' <param name="Key">键。</param>
    ''' <param name="Value">值。</param>
    ''' <remarks></remarks>
    Public Sub WriteIni(FileName As String, Key As String, Value As String)
        Try
            '预处理
            If Key.Contains(":") Then Throw New Exception($"尝试写入 ini 文件 {FileName} 的键名中包含了冒号：{Key}")
            Key = Key.Replace(vbCr, "").Replace(vbLf, "")
            Value = Value?.Replace(vbCr, "").Replace(vbLf, "")
            '防止争用
            SyncLock WriteIniLock
                '获取目前文件
                Dim Content As SafeDictionary(Of String, String) = IniGetContent(FileName)
                If Content Is Nothing Then Content = New SafeDictionary(Of String, String)
                '更新值
                If Value Is Nothing Then
                    If Not Content.ContainsKey(Key) Then Return '无需处理
                    Content.Remove(Key)
                Else
                    If Content.ContainsKey(Key) AndAlso Content(Key) = Value Then Return '无需处理
                    Content(Key) = Value
                End If
                '写入文件
                Dim FileContent As New StringBuilder
                For Each Pair In Content
                    FileContent.Append(Pair.Key)
                    FileContent.Append(":")
                    FileContent.Append(Pair.Value)
                    FileContent.Append(vbCrLf)
                Next
                If Not FileName.Contains(":\") Then FileName = $"{ExePath}PCL\{FileName}.ini"
                WriteFile(FileName, FileContent.ToString)
            End SyncLock
        Catch ex As Exception
            Log(ex, $"写入文件失败（{FileName} → {Key}:{Value}）", LogLevel.Hint)
        End Try
    End Sub
    Private WriteIniLock As New Object

    '路径处理
    ''' <summary>
    ''' 从文件路径或者 Url 获取不包含文件名的路径，或获取文件夹的父文件夹路径。
    ''' 取决于原路径格式，路径以 / 或 \ 结尾。
    ''' 不包含路径将会抛出异常。
    ''' </summary>
    Public Function GetPathFromFullPath(FilePath As String) As String
        If Not (FilePath.Contains("\") OrElse FilePath.Contains("/")) Then Throw New Exception("不包含路径：" & FilePath)
        If FilePath.EndsWithF("\") OrElse FilePath.EndsWithF("/") Then
            '是文件夹路径
            Dim IsRight As Boolean = FilePath.EndsWithF("\")
            FilePath = Left(FilePath, Len(FilePath) - 1)
            GetPathFromFullPath = Left(FilePath, FilePath.LastIndexOfAny({"\", "/"})) & If(IsRight, "\", "/")
        Else
            '是文件路径
            GetPathFromFullPath = Left(FilePath, FilePath.LastIndexOfAny({"\", "/"}) + 1)
            If GetPathFromFullPath = "" Then Throw New Exception("不包含路径：" & FilePath)
        End If
    End Function
    ''' <summary>
    ''' 从文件路径或者 Url 获取不包含路径的文件名。不包含文件名将会抛出异常。
    ''' </summary>
    Public Function GetFileNameFromPath(FilePath As String) As String
        FilePath = FilePath.Replace("/", "\")
        If FilePath.EndsWithF("\") Then Throw New Exception("不包含文件名：" & FilePath)
        If FilePath.Contains("?") Then FilePath = FilePath.Substring(0, FilePath.IndexOfF("?")) '去掉网络参数后的 ?
        If FilePath.Contains("\") Then FilePath = FilePath.Substring(FilePath.LastIndexOfF("\") + 1)
        Dim length As Integer = FilePath.Length
        If length = 0 Then Throw New Exception("不包含文件名：" & FilePath)
        If length > 250 Then Throw New PathTooLongException("文件名过长：" & FilePath)
        Return FilePath
    End Function
    ''' <summary>
    ''' 从文件路径或者 Url 获取不包含路径与扩展名的文件名。不包含文件名将会抛出异常。
    ''' </summary>
    Public Function GetFileNameWithoutExtentionFromPath(FilePath As String) As String
        Return IO.Path.GetFileNameWithoutExtension(FilePath)
    End Function
    ''' <summary>
    ''' 从文件夹路径获取文件夹名。
    ''' </summary>
    Public Function GetFolderNameFromPath(FolderPath As String) As String
        If FolderPath.EndsWithF(":\") OrElse FolderPath.EndsWithF(":\\") Then Return FolderPath.Substring(0, 1)
        If FolderPath.EndsWithF("\") OrElse FolderPath.EndsWithF("/") Then FolderPath = Left(FolderPath, FolderPath.Length - 1)
        Return GetFileNameFromPath(FolderPath)
    End Function

    '读取、写入、复制文件
    ''' <summary>
    ''' 复制文件。会自动创建文件夹、会覆盖已有的文件。
    ''' </summary>
    Public Sub CopyFile(FromPath As String, ToPath As String)
        Try
            '还原文件路径
            If Not FromPath.Contains(":\") Then FromPath = ExePath & FromPath
            If Not ToPath.Contains(":\") Then ToPath = ExePath & ToPath
            '如果复制同一个文件则跳过
            If FromPath = ToPath Then Return
            '确保目录存在
            Directory.CreateDirectory(GetPathFromFullPath(ToPath))
            '复制文件
            File.Copy(FromPath, ToPath, True)
        Catch ex As Exception
            Throw New Exception("复制文件出错：" & FromPath & " → " & ToPath, ex)
        End Try
    End Sub
    ''' <summary>
    ''' 读取文件，如果失败则返回空数组。
    ''' </summary>
    Public Function ReadFileBytes(FilePath As String, Optional Encoding As Encoding = Nothing) As Byte()
        Try
            '还原文件路径
            If Not FilePath.Contains(":\") Then FilePath = ExePath & FilePath
            If File.Exists(FilePath) Then
                Using ReadStream As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite) '支持读取使用中的文件
                    Using ms As New MemoryStream
                        ReadStream.CopyTo(ms)
                        Return ms.ToArray()
                    End Using
                End Using
            Else
                Log("[System] 欲读取的文件不存在，已返回空内容：" & FilePath)
                Return {}
            End If
        Catch ex As Exception
            Log(ex, "读取文件出错：" & FilePath)
            Return {}
        End Try
    End Function
    ''' <summary>
    ''' 读取文件，如果失败则返回空字符串。
    ''' </summary>
    ''' <param name="FilePath">文件完整或相对路径。</param>
    Public Function ReadFile(FilePath As String, Optional Encoding As Encoding = Nothing) As String
        Dim FileBytes = ReadFileBytes(FilePath)
        ReadFile = If(Encoding Is Nothing, DecodeBytes(FileBytes), Encoding.GetString(FileBytes))
    End Function
    ''' <summary>
    ''' 读取流中的所有文本。
    ''' </summary>
    Public Function ReadFile(Stream As Stream, Optional Encoding As Encoding = Nothing) As String
        Try
            Dim readedContent As New MemoryStream()
            Stream.CopyTo(readedContent)
            Dim Bts = readedContent.ToArray()
            Return If(Encoding, EncodingDetector.DetectEncoding(Bts)).GetString(Bts)
        Catch ex As Exception
            Log(ex, "读取流出错")
            Return ""
        End Try
    End Function
    ''' <summary>
    ''' 写入文件。
    ''' </summary>
    ''' <param name="FilePath">文件完整或相对路径。</param>
    ''' <param name="Text">文件内容。</param>
    ''' <param name="Append">是否将文件内容追加到当前文件，而不是覆盖它。</param>
    Public Sub WriteFile(FilePath As String, Text As String, Optional Append As Boolean = False, Optional Encoding As Encoding = Nothing)
        '处理相对路径
        If Not FilePath.Contains(":\") Then FilePath = ExePath & FilePath
        '确保目录存在
        Directory.CreateDirectory(GetPathFromFullPath(FilePath))
        '写入文件
        If Append Then
            '追加目前文件
            Using writer As New StreamWriter(FilePath, True, If(Encoding, EncodingDetector.DetectEncoding(ReadFileBytes(FilePath))))
                writer.Write(Text)
            End Using
        Else
            '直接写入字节
            File.WriteAllBytes(FilePath, If(Encoding Is Nothing, New UTF8Encoding(False).GetBytes(Text), Encoding.GetBytes(Text)))
        End If
    End Sub
    ''' <summary>
    ''' 写入文件。
    ''' 如果 CanThrow 设置为 False，返回是否写入成功。
    ''' </summary>
    ''' <param name="FilePath">文件完整或相对路径。</param>
    ''' <param name="Content">文件内容。</param>
    ''' <param name="Append">是否将文件内容追加到当前文件，而不是覆盖它。</param>
    Public Sub WriteFile(FilePath As String, Content As Byte(), Optional Append As Boolean = False)
        '处理相对路径
        If Not FilePath.Contains(":\") Then FilePath = ExePath & FilePath
        '确保目录存在
        Directory.CreateDirectory(GetPathFromFullPath(FilePath))
        '写入文件
        File.WriteAllBytes(FilePath, Content)
    End Sub
    ''' <summary>
    ''' 将流写入文件。
    ''' </summary>
    ''' <param name="FilePath">文件完整或相对路径。</param>
    Public Function WriteFile(FilePath As String, Stream As Stream) As Boolean
        Try
            '还原文件路径
            If Not FilePath.Contains(":\") Then FilePath = ExePath & FilePath
            '确保目录存在
            Directory.CreateDirectory(GetPathFromFullPath(FilePath))
            '读取流
            Using fs As New FileStream(FilePath, FileMode.Create, FileAccess.Write)
                fs.SetLength(0)
                Stream.CopyTo(fs)
            End Using
            Return True
        Catch ex As Exception
            Log(ex, "保存流出错")
            Return False
        End Try
    End Function
    
    ''' <summary>
    ''' 解码 Bytes。
    ''' </summary>
    Public Function DecodeBytes(Bytes As Byte()) As String
        Dim Length As Integer = Bytes.Length
        If Length < 3 Then Return Encoding.UTF8.GetString(Bytes)
        '根据 BOM 判断编码
        If Bytes(0) >= &HEF Then
            '有 BOM 类型
            If Bytes(0) = &HEF AndAlso Bytes(1) = &HBB Then
                Return Encoding.UTF8.GetString(Bytes, 3, Length - 3)
            ElseIf Bytes(0) = &HFE AndAlso Bytes(1) = &HFF Then
                Return Encoding.BigEndianUnicode.GetString(Bytes, 3, Length - 3)
            ElseIf Bytes(0) = &HFF AndAlso Bytes(1) = &HFE Then
                Return Encoding.Unicode.GetString(Bytes, 3, Length - 3)
            Else
                Return Encoding.GetEncoding("GB18030").GetString(Bytes, 3, Length - 3)
            End If
        End If
        '无 BOM 文件：GB18030（ANSI）或 UTF8
        Dim UTF8 = Encoding.UTF8.GetString(Bytes)
        Dim ErrorChar As Char = Encoding.UTF8.GetString({239, 191, 189}).ToCharArray()(0)
        If UTF8.Contains(ErrorChar) Then
            Return Encoding.GetEncoding("GB18030").GetString(Bytes)
        Else
            Return UTF8
        End If
    End Function

    '文件校验
    ''' <summary>
    ''' 获取文件 MD5，若失败则返回空字符串。
    ''' </summary>
    Public Function GetFileMD5(FilePath As String) As String
        Dim Retry As Boolean = False
Re:
        Try
            '获取 MD5
            Using fs As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
                Return Hash.MD5Provider.Instance.ComputeHash(fs)
            End Using
        Catch ex As Exception
            If Retry OrElse TypeOf ex Is FileNotFoundException Then
                Log(ex, "获取文件 MD5 失败：" & FilePath)
                Return ""
            Else
                Retry = True
                Log(ex, "获取文件 MD5 可重试失败：" & FilePath, LogLevel.Normal)
                Thread.Sleep(RandomUtils.NextInt(200, 500))
                GoTo Re
            End If
        End Try
    End Function
    ''' <summary>
    ''' 获取文件 SHA512，若失败则返回空字符串。
    ''' </summary>
    Public Function GetFileSHA512(FilePath As String) As String
        Dim Retry As Boolean = False
Re:
        Try
            ''检测该文件是否在下载中，若在下载则放弃检测
            'If IgnoreOnDownloading AndAlso NetManage.Files.ContainsKey(FilePath) AndAlso NetManage.Files(FilePath).State <= NetState.Merge Then Return ""
            '获取 SHA512
            Using fs As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
                Return Core.Utils.Hash.SHA512Provider.Instance.ComputeHash(fs)
            End Using
        Catch ex As Exception
            If Retry OrElse TypeOf ex Is FileNotFoundException Then
                Log(ex, "获取文件 SHA512 失败：" & FilePath)
                Return ""
            Else
                Retry = True
                Log(ex, "获取文件 SHA512 可重试失败：" & FilePath, LogLevel.Normal)
                Thread.Sleep(RandomUtils.NextInt(200, 500))
                GoTo Re
            End If
        End Try
    End Function
    ''' <summary>
    ''' 获取文件 SHA256，若失败则返回空字符串。
    ''' </summary>
    Public Function GetFileSHA256(FilePath As String) As String
        Dim Retry As Boolean = False
Re:
        Try
            ''检测该文件是否在下载中，若在下载则放弃检测
            'If IgnoreOnDownloading AndAlso NetManage.Files.ContainsKey(FilePath) AndAlso NetManage.Files(FilePath).State <= NetState.Merge Then Return ""
            '获取 SHA256
            Using fs As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
                Return Core.Utils.Hash.SHA256Provider.Instance.ComputeHash(fs)
            End Using
        Catch ex As Exception
            If Retry OrElse TypeOf ex Is FileNotFoundException Then
                Log(ex, "获取文件 SHA256 失败：" & FilePath)
                Return ""
            Else
                Retry = True
                Log(ex, "获取文件 SHA256 可重试失败：" & FilePath, LogLevel.Normal)
                Thread.Sleep(RandomUtils.NextInt(200, 500))
                GoTo Re
            End If
        End Try
    End Function
    ''' <summary>
    ''' 获取文件 SHA1，若失败则返回空字符串。
    ''' </summary>
    Public Function GetFileSHA1(FilePath As String) As String
        Dim Retry As Boolean = False
Re:
        Try
            '获取 SHA1
            Using fs As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
                Return Core.Utils.Hash.SHA1Provider.Instance.ComputeHash(fs)
            End Using
        Catch ex As Exception
            If Retry OrElse TypeOf ex Is FileNotFoundException Then
                Log(ex, "获取文件 SHA1 失败：" & FilePath)
                Return ""
            Else
                Retry = True
                Log(ex, "获取文件 SHA1 可重试失败：" & FilePath, LogLevel.Normal)
                Thread.Sleep(RandomUtils.NextInt(200, 500))
                GoTo Re
            End If
        End Try
    End Function
    ''' <summary>
    ''' 获取流的 SHA1，若失败则返回空字符串。
    ''' </summary>
    Public Function GetAuthSHA1(inputStream As Stream) As String
        Try
            Return Core.Utils.Hash.SHA1Provider.Instance.ComputeHash(inputStream)
        Catch ex As Exception
            Log(ex, "获取流 SHA1 失败")
            Return ""
        End Try
    End Function
    ''' <summary>
    ''' 文件的校验规则。
    ''' </summary>
    Public Class FileChecker
        ''' <summary>
        ''' 文件的准确大小。
        ''' </summary>
        Public ActualSize As Long = -1
        ''' <summary>
        ''' 文件的最小大小。
        ''' </summary>
        Public MinSize As Long = -1
        ''' <summary>
        ''' 文件的 MD5、SHA1 或 SHA256。会根据输入字符串的长度自动判断种类。
        ''' </summary>
        Public Hash As String = Nothing
        ''' <summary>
        ''' 是否可以使用已经存在的文件。
        ''' </summary>
        Public CanUseExistsFile As Boolean = True
        ''' <summary>
        ''' 是否为 Json 文件。
        ''' </summary>
        Public IsJson As Boolean = False
        Public Sub New(Optional MinSize As Long = -1, Optional ActualSize As Long = -1, Optional Hash As String = Nothing, Optional CanUseExistsFile As Boolean = True, Optional IsJson As Boolean = False)
            Me.ActualSize = ActualSize
            Me.MinSize = MinSize
            Me.Hash = Hash
            Me.CanUseExistsFile = CanUseExistsFile
            Me.IsJson = IsJson
        End Sub
        ''' <summary>
        ''' 检查文件。若成功则返回 Nothing，失败则返回错误的描述文本，描述文本不以句号结尾。不会抛出错误。
        ''' </summary>
        Public Function Check(LocalPath As String) As String
            Try
                Log($"[Checker] 开始校验文件 {LocalPath}", LogLevel.Developer)
                Dim Info As New FileInfo(LocalPath)
                If Not Info.Exists Then Return "文件不存在：" & LocalPath
                Dim FileSize As Long = Info.Length
                Dim ErrorMessage As New List(Of String)
                Dim AllowIgnore As Boolean = False '允许相信哈希正确但是大小不正确
                If Not String.IsNullOrEmpty(Hash) Then
                    If Hash.Length < 35 Then 'MD5
                        Dim ComputedHash As String = GetFileMD5(LocalPath)
                        If Hash.ToLowerInvariant <> ComputedHash Then
                            ErrorMessage.Add("文件 MD5 应为 " & Hash & "，实际为 " & ComputedHash)
                        End If
                    ElseIf Hash.Length = 64 Then 'SHA256
                        Dim ComputedHash As String = GetFileSHA256(LocalPath)
                        If Hash.ToLowerInvariant <> ComputedHash Then
                            ErrorMessage.Add("文件 SHA256 应为 " & Hash & "，实际为 " & ComputedHash)
                        End If
                    Else 'SHA1 (40)
                        Dim ComputedHash As String = GetFileSHA1(LocalPath)
                        If Hash.ToLowerInvariant <> ComputedHash Then
                            ErrorMessage.Add("文件 SHA1 应为 " & Hash & "，实际为 " & ComputedHash)
                        End If
                    End If
                    AllowIgnore = ErrorMessage.Count = 0
                End If

                If ActualSize >= 0 AndAlso ActualSize <> FileSize AndAlso Not AllowIgnore Then '不允许忽略大小不正确的情况
                    ErrorMessage.Add($"文件大小应为 {ActualSize} B，实际为 {FileSize} B" &
                        If(FileSize < 2000, "，内容为" & ReadFile(LocalPath), ""))
                End If

                If MinSize >= 0 AndAlso MinSize > FileSize Then
                    ErrorMessage.Add($"文件大小应大于 {MinSize} B，实际为 {FileSize} B" &
                        If(FileSize < 2000, "，内容为：" & ReadFile(LocalPath), ""))
                End If

                If IsJson Then
                    Dim Content As String = ReadFile(LocalPath)
                    If Content = "" Then Throw New Exception("读取到的文件为空")
                    Try
                        GetJson(Content)
                    Catch ex As Exception
                        Throw New Exception("不是有效的 Json 文件", ex)
                    End Try
                End If
                If ErrorMessage.Count <> 0 Then
                    ErrorMessage.Insert(0, $"实际校验地址：{LocalPath}")
                    Return ErrorMessage.Join(";")
                End If
                Return Nothing
            Catch ex As Exception
                Log(ex, "检查文件出错")
                Return ex.ToString()
            End Try
        End Function
    End Class

    ''' <summary>
    ''' 尝试根据后缀名判断文件种类并解压文件，支持 gz 与 zip，会尝试将 Jar 以 zip 方式解压。
    ''' 会尝试创建，但不会清空目标文件夹。
    ''' </summary>
    Public Sub ExtractFile(CompressFilePath As String, DestDirectory As String, Optional Encode As Encoding = Nothing,
                           Optional ProgressIncrementHandler As Action(Of Double) = Nothing)
        Directory.CreateDirectory(DestDirectory)
        If CompressFilePath.EndsWithF(".gz", True) Then
            '以 gz 方式解压
            Using compressedFile As New FileStream(CompressFilePath, FileMode.Open, FileAccess.Read)
                Using decompressStream As New GZipStream(compressedFile, CompressionMode.Decompress)
                    Using extractFileStream As New FileStream(IO.Path.Combine(DestDirectory, GetFileNameFromPath(CompressFilePath).ToLower.Replace(".tar", "").Replace(".gz", "")), FileMode.OpenOrCreate, FileAccess.Write)
                        decompressStream.CopyTo(extractFileStream)
                    End Using
                End Using
            End Using
        Else
            '以 zip 方式解压
            Using Archive = ZipFile.Open(CompressFilePath, ZipArchiveMode.Read, If(Encode, Encoding.GetEncoding("GB18030")))
                Dim TotalCount As Integer = Archive.Entries.Count
                For Each Entry As ZipArchiveEntry In Archive.Entries
                    If ProgressIncrementHandler IsNot Nothing Then ProgressIncrementHandler(1 / TotalCount)
                    Dim DestinationPath As String = IO.Path.Combine(DestDirectory, Entry.FullName)
                    If DestinationPath.EndsWithF("\") OrElse DestinationPath.EndsWithF("/") Then
                        Continue For '不创建空文件夹
                    Else
                        Directory.CreateDirectory(GetPathFromFullPath(DestinationPath))
                        Entry.ExtractToFile(DestinationPath, True)
                    End If
                Next
            End Using
        End If
    End Sub

    ''' <summary>
    ''' 删除文件夹，返回删除的文件个数。通过参数选择是否抛出异常。
    ''' </summary>
    Public Function DeleteDirectory(Path As String, Optional IgnoreIssue As Boolean = False) As Integer
        If Not Directory.Exists(Path) Then Return 0
        Dim DeletedCount As Integer = 0
        Dim Files As String()
        Try
            Files = Directory.GetFiles(Path)
        Catch ex As DirectoryNotFoundException '#4549
            Log(ex, $"疑似为孤立符号链接，尝试直接删除（{Path}）", LogLevel.Developer)
            Directory.Delete(Path)
            Return 0
        End Try
        For Each FilePath As String In Files
            Dim RetriedFile As Boolean = False
RetryFile:
            Try
                File.Delete(FilePath)
                DeletedCount += 1
            Catch ex As Exception
                If Not RetriedFile Then
                    RetriedFile = True
                    Log(ex, $"删除文件失败，将在 0.3s 后重试（{FilePath}）")
                    Thread.Sleep(300)
                    GoTo RetryFile
                ElseIf IgnoreIssue Then
                    Log(ex, "删除单个文件可忽略地失败")
                Else
                    Throw
                End If
            End Try
        Next
        For Each str As String In Directory.GetDirectories(Path)
            DeleteDirectory(str, IgnoreIssue)
        Next
        Dim RetriedDir As Boolean = False
RetryDir:
        Try
            Directory.Delete(Path, True)
        Catch ex As Exception
            If Not RetriedDir AndAlso Not RunInUi() Then
                RetriedDir = True
                Log(ex, $"删除文件夹失败，将在 0.3s 后重试（{Path}）")
                Thread.Sleep(300)
                GoTo RetryDir
            ElseIf IgnoreIssue Then
                Log(ex, "删除单个文件夹可忽略地失败")
            Else
                Throw
            End If
        End Try
        Return DeletedCount
    End Function
    ''' <summary>
    ''' 复制文件夹，失败会抛出异常。
    ''' </summary>
    Public Sub CopyDirectory(FromPath As String, ToPath As String, Optional ProgressIncrementHandler As Action(Of Double) = Nothing)
        FromPath = FromPath.Replace("/", "\")
        If Not FromPath.EndsWithF("\") Then FromPath &= "\"
        ToPath = ToPath.Replace("/", "\")
        If Not ToPath.EndsWithF("\") Then ToPath &= "\"
        Dim AllFiles = EnumerateFiles(FromPath).ToList
        Dim FileCount As Integer = AllFiles.Count
        For Each File In AllFiles
            CopyFile(File.FullName, File.FullName.Replace(FromPath, ToPath))
            If ProgressIncrementHandler IsNot Nothing Then ProgressIncrementHandler(1 / FileCount)
        Next
    End Sub
    ''' <summary>
    ''' 遍历文件夹中的所有文件。
    ''' </summary>
    Public Function EnumerateFiles(Directory As String) As IEnumerable(Of FileInfo)
        Dim Info As New DirectoryInfo(ShortenPath(Directory))
        If Not Info.Exists Then Return New List(Of FileInfo)
        Return Info.EnumerateFiles("*", SearchOption.AllDirectories)
    End Function

    ''' <summary>
    ''' 若路径长度大于指定值，则将长路径转换为短路径。
    ''' </summary>
    Public Function ShortenPath(LongPath As String, Optional ShortenThreshold As Integer = 247) As String
        If LongPath.Length <= ShortenThreshold Then Return LongPath
        Dim ShortPath As New StringBuilder(260)
        GetShortPathName(LongPath, ShortPath, 260)
        Return ShortPath.ToString
    End Function

    Public Sub MoveDirectory(SourceDir As String, TargetDir As String)
        If Not Directory.Exists(TargetDir) Then Directory.CreateDirectory(TargetDir)
        For Each FilePath In Directory.GetFiles(SourceDir)
            Dim FileName = GetFileNameFromPath(FilePath)
            File.Move(FilePath, IO.Path.Combine(TargetDir, FileName))
        Next
        For Each DirPath In Directory.GetDirectories(SourceDir)
            Dim DirName = GetFolderNameFromPath(DirPath)
            MoveDirectory(DirPath, IO.Path.Combine(TargetDir, DirName))
        Next
    End Sub
    Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" (ByVal lpszLongPath As String, ByVal lpszShortPath As StringBuilder, ByVal cchBuffer As Integer) As Integer

    Public Sub CreateSymbolicLink(ByVal LinkPath As String, ByVal TargetPath As String, ByVal Flags As Integer)
        Dim CMDProcess As New Process
        Dim LinkDPath = ExtractLinkD()
        With CMDProcess.StartInfo
            .FileName = LinkDPath
            .Arguments = $"""{LinkPath}"" ""{TargetPath}"""
            .CreateNoWindow = True
            .UseShellExecute = False
        End With
        CMDProcess.Start()
        While Not CMDProcess.HasExited
        End While
    End Sub
#End Region

#Region "文本"
    Public vbLQ As Char = Convert.ToChar(8220)
    Public vbRQ As Char = Convert.ToChar(8221)

    ''' <summary>
    ''' 返回一个枚举对应的字符串。
    ''' </summary>
    ''' <param name="EnumData">一个已经实例化的枚举类型。</param>
    Public Function GetStringFromEnum(EnumData As [Enum]) As String
        Return [Enum].GetName(EnumData.GetType, EnumData)
    End Function
    ''' <summary>
    ''' 将文件大小转化为适合的文本形式，如“1.28 M”。
    ''' </summary>
    ''' <param name="FileSize">以字节为单位的大小表示。</param>
    Public Function GetString(FileSize As Long) As String
        Dim IsNegative = FileSize < 0
        If IsNegative Then FileSize *= -1
        If FileSize < 1000 Then
            'B 级
            Return If(IsNegative, "-", "") & FileSize & " B"
        ElseIf FileSize < 1024 * 1000 Then
            'K 级
            Dim RoundResult As String = Math.Round(FileSize / 1024)
            Return If(IsNegative, "-", "") & Math.Round(FileSize / 1024, CInt(MathClamp(3 - RoundResult.Length, 0, 2))) & " K"
        ElseIf FileSize < 1024 * 1024 * 1000 Then
            'M 级
            Dim RoundResult As String = Math.Round(FileSize / 1024 / 1024)
            Return If(IsNegative, "-", "") & Math.Round(FileSize / 1024 / 1024, CInt(MathClamp(3 - RoundResult.Length, 0, 2))) & " M"
        Else
            'G 级
            Dim RoundResult As String = Math.Round(FileSize / 1024 / 1024 / 1024)
            Return If(IsNegative, "-", "") & Math.Round(FileSize / 1024 / 1024 / 1024, CInt(MathClamp(3 - RoundResult.Length, 0, 2))) & " G"
        End If
    End Function

    ''' <summary>
    ''' 获取 JSON 对象。
    ''' </summary>
    Public Function GetJson(Data As String)
        Try
            Return JsonConvert.DeserializeObject(Data, New JsonSerializerSettings With {.DateTimeZoneHandling = DateTimeZoneHandling.Local})
        Catch ex As Exception
            Dim Length As Integer = If(Data, "").Length
            Throw New Exception("格式化 JSON 失败：" & If(Length > 2000, Data.Substring(0, 500) & $"...(全长 {Length} 个字符)..." & Right(Data, 500), Data))
        End Try
    End Function

    ''' <summary>
    ''' 将第一个字符转换为大写，其余字符转换为小写。
    ''' </summary>
    <Extension> Public Function Capitalize(word As String) As String
        If String.IsNullOrEmpty(word) Then Return word
        Return word.Substring(0, 1).ToUpperInvariant() & word.Substring(1).ToLowerInvariant()
    End Function

    ''' <summary>
    ''' 将字符串统一至某个长度，过短则以 Code 将其右侧填充，过长则截取靠左的指定长度。
    ''' </summary>
    Public Function StrFill(Str As String, Code As String, Length As Byte) As String
        If Str.Length > Length Then Return Mid(Str, 1, Length)
        Return Mid(Str.PadRight(Length, Code), Str.Length + 1) & Str
    End Function
    ''' <summary>
    ''' 将一个小数显示为固定的小数点后位数形式，将向零取整。
    ''' 如 12 保留 2 位则输出 12.00，而 95.678 保留 2 位则输出 95.67。
    ''' </summary>
    Public Function StrFillNum(Num As Double, Length As Integer) As String
        Num = Math.Round(Num, Length, MidpointRounding.AwayFromZero)
        StrFillNum = Num
        If Not StrFillNum.Contains(".") Then Return (StrFillNum & ".").PadRight(StrFillNum.Length + 1 + Length, "0")
        Return StrFillNum.PadRight(StrFillNum.Split(".")(0).Length + 1 + Length, "0")
    End Function
    ''' <summary>
    ''' 移除字符串首尾的标点符号、回车，以及括号中、冒号后的补充说明内容。
    ''' </summary>
    Public Function StrTrim(Str As String, Optional RemoveQuote As Boolean = True)
        If RemoveQuote Then Str = Str.Split("（")(0).Split("：")(0).Split("(")(0).Split(":")(0)
        Return Str.Trim(".", "。", "！", " ", "!", "?", "？", vbCr, vbLf)
    End Function
    ''' <summary>
    ''' 连接字符串。
    ''' </summary>
    <Extension> Public Function Join(List As IEnumerable, Split As String) As String
        Dim Builder As New StringBuilder
        Dim IsFirst As Boolean = True
        For Each Element In List
            If IsFirst Then
                IsFirst = False
            Else
                Builder.Append(Split)
            End If
            If Element IsNot Nothing Then Builder.Append(Element)
        Next
        Return Builder.ToString
    End Function
    ''' <summary>
    ''' 分割字符串。
    ''' </summary>
    <Extension> Public Function Split(FullStr As String, SplitStr As String) As String()
        If SplitStr.Length = 1 Then
            Return FullStr.Split(SplitStr(0))
        Else
            Return FullStr.Split({SplitStr}, StringSplitOptions.None)
        End If
    End Function

    ''' <summary>
    ''' 获取字符串哈希值。
    ''' </summary>
    Public Function GetHash(Str As String) As ULong
        GetHash = 5381
        For i = 0 To Str.Length - 1
            GetHash = (GetHash << 5) Xor GetHash Xor CType(AscW(Str(i)), ULong)
        Next
        Return GetHash Xor &HA98F501BC684032FUL
    End Function
    ''' <summary>
    ''' 获取字符串 MD5。
    ''' </summary>
    Public Function GetStringMD5(Str As String) As String
        Return Core.Utils.Hash.MD5Provider.Instance.ComputeHash(Str)
    End Function
    ''' <summary>
    ''' 检查字符串中的字符是否均为 ASCII 字符。
    ''' </summary>
    <Extension> Public Function IsASCII(Input As String) As Boolean
        Return Input.All(Function(c) AscW(c) < 128)
    End Function

    ''' <summary>
    ''' 获取在子字符串第一次出现之前的部分，例如对 2024/11/08 拆切 / 会得到 2024。
    ''' 如果未找到子字符串则不裁切。
    ''' </summary>
    <Extension> Public Function BeforeFirst(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
        Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.IndexOfF(Text, IgnoreCase))
        If Pos >= 0 Then
            Return Str.Substring(0, Pos)
        Else
            Return Str
        End If
    End Function
    ''' <summary>
    ''' 获取在子字符串最后一次出现之前的部分，例如对 2024/11/08 拆切 / 会得到 2024/11。
    ''' 如果未找到子字符串则不裁切。
    ''' </summary>
    <Extension> Public Function BeforeLast(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
        Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.LastIndexOfF(Text, IgnoreCase))
        If Pos >= 0 Then
            Return Str.Substring(0, Pos)
        Else
            Return Str
        End If
    End Function
    ''' <summary>
    ''' 获取在子字符串第一次出现之后的部分，例如对 2024/11/08 拆切 / 会得到 11/08。
    ''' 如果未找到子字符串则不裁切。
    ''' </summary>
    <Extension> Public Function AfterFirst(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
        Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.IndexOfF(Text, IgnoreCase))
        If Pos >= 0 Then
            Return Str.Substring(Pos + Text.Length)
        Else
            Return Str
        End If
    End Function
    ''' <summary>
    ''' 获取在子字符串最后一次出现之后的部分，例如对 2024/11/08 拆切 / 会得到 08。
    ''' 如果未找到子字符串则不裁切。
    ''' </summary>
    <Extension> Public Function AfterLast(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
        Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.LastIndexOfF(Text, IgnoreCase))
        If Pos >= 0 Then
            Return Str.Substring(Pos + Text.Length)
        Else
            Return Str
        End If
    End Function
    ''' <summary>
    ''' 获取处于两个子字符串之间的部分，裁切尽可能多的内容。
    ''' 等效于 AfterLast 后接 BeforeFirst。
    ''' 如果未找到子字符串则不裁切。
    ''' </summary>
    <Extension> Public Function Between(Str As String, After As String, Before As String, Optional IgnoreCase As Boolean = False) As String
        Dim StartPos As Integer = If(String.IsNullOrEmpty(After), -1, Str.LastIndexOfF(After, IgnoreCase))
        If StartPos >= 0 Then
            StartPos += After.Length
        Else
            StartPos = 0
        End If
        Dim EndPos As Integer = If(String.IsNullOrEmpty(Before), -1, Str.IndexOfF(Before, StartPos, IgnoreCase))
        If EndPos >= 0 Then
            Return Str.Substring(StartPos, EndPos - StartPos)
        ElseIf StartPos > 0 Then
            Return Str.Substring(StartPos)
        Else
            Return Str
        End If
    End Function

    ''' <summary>
    ''' 高速的 StartsWith。
    ''' </summary>
    <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
    Public Function StartsWithF(Str As String, Prefix As String, Optional IgnoreCase As Boolean = False) As Boolean
        Return Str.StartsWith(Prefix, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal))
    End Function
    ''' <summary>
    ''' 高速的 EndsWith。
    ''' </summary>
    <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
    Public Function EndsWithF(Str As String, Suffix As String, Optional IgnoreCase As Boolean = False) As Boolean
        Return Str.EndsWith(Suffix, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal))
    End Function
    ''' <summary>
    ''' 支持可变大小写判断的 Contains。
    ''' </summary>
    <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
    Public Function ContainsF(Str As String, SubStr As String, Optional IgnoreCase As Boolean = False) As Boolean
        Return Str.IndexOf(SubStr, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal)) >= 0
    End Function
    ''' <summary>
    ''' 高速的 IndexOf。
    ''' </summary>
    <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
    Public Function IndexOfF(Str As String, SubStr As String, Optional IgnoreCase As Boolean = False) As Integer
        Return Str.IndexOf(SubStr, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal))
    End Function
    ''' <summary>
    ''' 高速的 IndexOf。
    ''' </summary>
    <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
    Public Function IndexOfF(Str As String, SubStr As String, StartIndex As Integer, Optional IgnoreCase As Boolean = False) As Integer
        Return Str.IndexOf(SubStr, StartIndex, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal))
    End Function
    ''' <summary>
    ''' 高速的 LastIndexOf。
    ''' </summary>
    <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
    Public Function LastIndexOfF(Str As String, SubStr As String, Optional IgnoreCase As Boolean = False) As Integer
        Return Str.LastIndexOf(SubStr, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal))
    End Function
    ''' <summary>
    ''' 高速的 LastIndexOf。
    ''' </summary>
    <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
    Public Function LastIndexOfF(Str As String, SubStr As String, StartIndex As Integer, Optional IgnoreCase As Boolean = False) As Integer
        Return Str.LastIndexOf(SubStr, StartIndex, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal))
    End Function

    ''' <summary>
    ''' 不会报错的 Val。
    ''' 如果输入有误，返回 0。
    ''' </summary>
    Public Function Val(Str As Object) As Double
        Try
            Return If(TypeOf Str Is String AndAlso Str = "&", 0, Conversion.Val(Str))
        Catch
            Return 0
        End Try
    End Function

    '转义
    ''' <summary>
    ''' 为字符串进行 XML 转义。
    ''' </summary>
    Public Function EscapeXML(Str As String) As String
        If Str.StartsWithF("{") Then Str = "{}" & Str '#4187
        Return Str.
            Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("'", "&apos;").
            Replace("""", "&quot;").Replace(vbCrLf, "&#xa;")
    End Function
    ''' <summary>
    ''' 为字符串进行 Like 关键字转义。
    ''' </summary>
    Public Function EscapeLikePattern(input As String) As String
        Dim sb As New StringBuilder()
        For Each c As Char In input
            Select Case c
                Case "["c, "]"c, "*"c, "?"c, "#"c
                    sb.Append("["c).Append(c).Append("]"c)
                Case Else
                    sb.Append(c)
            End Select
        Next
        Return sb.ToString()
    End Function

    '正则
    ''' <summary>
    ''' 搜索字符串中的所有正则匹配项。
    ''' </summary>
    <Extension> Public Function RegexSearch(str As String, regex As String, Optional options As RegexOptions = RegexOptions.None) As List(Of String)
        Try
            RegexSearch = New List(Of String)
            Dim RegexSearchRes = New Regex(regex, options).Matches(str)
            If RegexSearchRes Is Nothing Then Return RegexSearch
            For Each item As Match In RegexSearchRes
                RegexSearch.Add(item.Value)
            Next
        Catch ex As Exception
            Log(ex, "正则匹配全部项出错")
            Return New List(Of String)
        End Try
    End Function
    ''' <summary>
    ''' 获取字符串中的第一个正则匹配项，若无匹配则返回 Nothing。
    ''' </summary>
    <Extension> Public Function RegexSeek(str As String, regex As String, Optional options As RegexOptions = RegexOptions.None) As String
        Try
            Dim Result = RegularExpressions.Regex.Match(str, regex, options).Value
            Return If(Result = "", Nothing, Result)
        Catch ex As Exception
            Log(ex, "正则匹配第一项出错")
            Return Nothing
        End Try
    End Function
    ''' <summary>
    ''' 检查字符串是否匹配某正则模式。
    ''' </summary>
    <Extension> Public Function RegexCheck(str As String, regex As String, Optional options As RegexOptions = RegexOptions.None) As Boolean
        Try
            Return RegularExpressions.Regex.IsMatch(str, regex, options)
        Catch ex As Exception
            Log(ex, "正则检查出错")
            Return False
        End Try
    End Function
    ''' <summary>
    ''' 进行正则替换，会抛出错误。
    ''' </summary>
    <Extension> Public Function RegexReplace(AllContents As String, SearchRegex As String, ReplaceTo As String, Optional options As RegexOptions = RegexOptions.None) As String
        Return Regex.Replace(AllContents, SearchRegex, ReplaceTo, options)
    End Function
    ''' <summary>
    ''' 对每个正则匹配分别进行替换，会抛出错误。
    ''' </summary>
    <Extension> Public Function RegexReplaceEach(AllContents As String, SearchRegex As String, ReplaceTo As MatchEvaluator, Optional options As RegexOptions = RegexOptions.None) As String
        Return Regex.Replace(AllContents, SearchRegex, ReplaceTo, options)
    End Function

#End Region

#Region "搜索"

    ''' <summary>
    ''' 获取搜索文本的相似度。
    ''' </summary>
    ''' <param name="Source">被搜索的长内容。</param>
    ''' <param name="Query">用户输入的搜索文本。</param>
    Private Function SearchSimilarity(Source As String, Query As String) As Double
        Dim qp As Integer = 0, lenSum As Double = 0
        Source = Source.ToLower.Replace(" ", "")
        Query = Query.ToLower.Replace(" ", "")
        Dim sourceLength As Integer = Source.Length, queryLength As Integer = Query.Length '用于计算最后因数的长度缓存
        Do While qp < queryLength
            '对 qp 作为开始位置计算
            Dim sp As Integer = 0, lenMax As Integer = 0, spMax As Integer = 0
            '查找以 qp 为头的最大子串
            Do While sp < Source.Length
                '对每个 sp 作为开始位置计算最大子串
                Dim len As Integer = 0
                Do While (qp + len) < queryLength AndAlso (sp + len) < Source.Length AndAlso Source(sp + len) = Query(qp + len)
                    len += 1
                Loop
                '存储 len
                If len > lenMax Then
                    lenMax = len
                    spMax = sp
                End If
                '根据结果增加 sp
                sp += Math.Max(1, len)
            Loop
            If lenMax > 0 Then
                Source = Source.Substring(0, spMax) & If(Source.Count > spMax + lenMax, Source.Substring(spMax + lenMax), String.Empty) '将源中的对应字段替换空
                '存储 lenSum
                Dim IncWeight = (Math.Pow(1.4, 3 + lenMax) - 3.6) '根据长度加成
                IncWeight *= 1 + 0.3 * Math.Max(0, 3 - Math.Abs(qp - spMax)) '根据位置加成
                lenSum += IncWeight
            End If
            '根据结果增加 qp
            qp += Math.Max(1, lenMax)
        Loop
        '计算结果：重复字段量 × 源长度影响比例
        Return (lenSum / queryLength) * (3 / Math.Pow(sourceLength + 15, 0.5)) * If(queryLength <= 2, 3 - queryLength, 1)
    End Function
    ''' <summary>
    ''' 获取多段文本加权后的相似度。
    ''' </summary>
    Private Function SearchSimilarityWeighted(Source As List(Of KeyValuePair(Of String, Double)), Query As String) As Double
        Dim TotalWeight As Double = 0
        Dim Sum As Double = 0
        For Each Pair In Source
            Sum += SearchSimilarity(Pair.Key, Query) * Pair.Value
            TotalWeight += Pair.Value
        Next
        Return Sum / TotalWeight
    End Function
    ''' <summary>
    ''' 用于搜索的项目。
    ''' </summary>
    Public Class SearchEntry(Of T)
        ''' <summary>
        ''' 该项目对应的源数据。
        ''' </summary>
        Public Item As T
        ''' <summary>
        ''' 该项目用于搜索的源。
        ''' </summary>
        Public SearchSource As List(Of KeyValuePair(Of String, Double))
        ''' <summary>
        ''' 相似度。
        ''' </summary>
        Public Similarity As Double
        ''' <summary>
        ''' 是否完全匹配。
        ''' </summary>
        Public AbsoluteRight As Boolean
    End Class
    ''' <summary>
    ''' 进行多段文本加权搜索，获取相似度较高的数项结果。
    ''' </summary>
    ''' <param name="MaxBlurCount">返回的最大模糊结果数。</param>
    ''' <param name="MinBlurSimilarity">返回结果要求的最低相似度。</param>
    Public Function Search(Of T)(Entries As List(Of SearchEntry(Of T)), Query As String, Optional MaxBlurCount As Integer = 5, Optional MinBlurSimilarity As Double = 0.1) As List(Of SearchEntry(Of T))
        Dim ResultList As New List(Of SearchEntry(Of T))

        If Entries Is Nothing OrElse Not Entries.Any() Then
            Return ResultList
        End If

        ' Preprocess query into parts
        Dim queryParts As String() = Query.Split(New Char() {" "c}, StringSplitOptions.RemoveEmptyEntries)
        If queryParts.Length = 0 Then
            ResultList.AddRange(Entries)
            Return ResultList
        End If

        ' Precompute query parts in lowercase for case-insensitive comparison
        Dim queryPartsLower As String() = queryParts.Select(Function(q) q.ToLower()).ToArray()

        ' Process each entry to compute similarity and absolute match status
        For Each Entry In Entries
            Entry.Similarity = SearchSimilarityWeighted(Entry.SearchSource, Query)

            ' Preprocess search source keys: remove spaces and convert to lowercase
            Dim processedSources = Entry.SearchSource.Select(Function(s) s.Key.Replace(" ", "").ToLower()).ToList()

            ' Check if all query parts are matched exactly by at least one source
            Dim isAbsoluteRight As Boolean = True
            For Each qp In queryPartsLower
                Dim found = False
                For Each ps In processedSources
                    If ps.Contains(qp) Then
                        found = True
                        Exit For
                    End If
                Next
                If Not found Then
                    isAbsoluteRight = False
                    Exit For
                End If
            Next
            Entry.AbsoluteRight = isAbsoluteRight
        Next

        ' Sort by absolute match (descending), then by similarity (descending)
        Dim sortedEntries = Entries.OrderByDescending(Function(e) e.AbsoluteRight).ThenByDescending(Function(e) e.Similarity).ToList()

        ' Build the final result list
        Dim blurCount As Integer = 0
        For Each Entry In sortedEntries
            If Entry.AbsoluteRight Then
                ResultList.Add(Entry)
            Else
                If Entry.Similarity < MinBlurSimilarity OrElse blurCount >= MaxBlurCount Then
                    Exit For
                End If
                ResultList.Add(Entry)
                blurCount += 1
            End If
        Next

        Return ResultList
    End Function

#End Region

#Region "系统"

    Public Function IsUtf8CodePage() As Boolean
        Return Encoding.Default.CodePage = 65001
    End Function

    ''' <summary>
    ''' 线程安全的 List。
    ''' 通过在 For Each 循环中使用一个浅表副本规避多线程操作或移除自身导致的异常。
    ''' </summary>
    Public Class SafeList(Of T)
        Inherits List(Of T)
        Implements IEnumerable, IEnumerable(Of T)

        Private ReadOnly SyncRoot As New Object
        '构造函数
        Public Sub New()
            MyBase.New()
        End Sub
        Public Sub New(Data As IEnumerable(Of T))
            MyBase.New(Data)
        End Sub
        Public Shared Function FromList(data As List(Of T)) As SafeList(Of T)
            Return New SafeList(Of T)(data)
        End Function
        Public Function ToList() As List(Of T)
            SyncLock SyncRoot
                Return MyBase.ToList() ' 创建副本
            End SyncLock
        End Function
        '基于 SyncLock 覆写
        Public Overloads Function GetEnumerator() As IEnumerator(Of T) Implements IEnumerable(Of T).GetEnumerator
            SyncLock SyncRoot
                Return MyBase.ToList.GetEnumerator()
            End SyncLock
        End Function
        Private Overloads Function GetEnumeratorGeneral() As IEnumerator Implements IEnumerable.GetEnumerator
            SyncLock SyncRoot
                Return MyBase.ToList.GetEnumerator()
            End SyncLock
        End Function
    End Class

    ''' <summary>
    ''' 线程安全的字典。
    ''' 通过在 For Each 循环中使用一个浅表副本规避多线程操作或移除自身导致的异常。
    ''' </summary>
    Public Class SafeDictionary(Of TKey, TValue)
        Implements IDictionary(Of TKey, TValue)
        Implements IEnumerable(Of KeyValuePair(Of TKey, TValue))

        Private ReadOnly SyncRoot As New Object
        Private ReadOnly _Dictionary As New Dictionary(Of TKey, TValue)

        '构造函数
        Public Sub New()
        End Sub
        Public Sub New(data As IEnumerable(Of KeyValuePair(Of TKey, TValue)))
            For Each DataItem In data
                _Dictionary.Add(DataItem.Key, DataItem.Value)
            Next
        End Sub

        '线程安全的方法实现
        Public Sub Add(key As TKey, value As TValue) Implements IDictionary(Of TKey, TValue).Add
            SyncLock SyncRoot
                _Dictionary.Add(key, value)
            End SyncLock
        End Sub
        Public Function ContainsKey(key As TKey) As Boolean Implements IDictionary(Of TKey, TValue).ContainsKey
            SyncLock SyncRoot
                Return _Dictionary.ContainsKey(key)
            End SyncLock
        End Function
        Public ReadOnly Property Keys As ICollection(Of TKey) Implements IDictionary(Of TKey, TValue).Keys
            Get
                SyncLock SyncRoot
                    Return New List(Of TKey)(_Dictionary.Keys)
                End SyncLock
            End Get
        End Property
        Public Function Remove(key As TKey) As Boolean Implements IDictionary(Of TKey, TValue).Remove
            SyncLock SyncRoot
                Return _Dictionary.Remove(key)
            End SyncLock
        End Function
        Public Function TryGetValue(key As TKey, ByRef value As TValue) As Boolean Implements IDictionary(Of TKey, TValue).TryGetValue
            SyncLock SyncRoot
                Return _Dictionary.TryGetValue(key, value)
            End SyncLock
        End Function
        Public ReadOnly Property Values As ICollection(Of TValue) Implements IDictionary(Of TKey, TValue).Values
            Get
                SyncLock SyncRoot
                    Return New List(Of TValue)(_Dictionary.Values)
                End SyncLock
            End Get
        End Property
        Default Public Property Item(key As TKey) As TValue Implements IDictionary(Of TKey, TValue).Item
            Get
                SyncLock SyncRoot
                    Return _Dictionary(key)
                End SyncLock
            End Get
            Set(value As TValue)
                SyncLock SyncRoot
                    _Dictionary(key) = value
                End SyncLock
            End Set
        End Property
        Public Sub Add(item As KeyValuePair(Of TKey, TValue)) Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Add
            SyncLock SyncRoot
                _Dictionary.Add(item.Key, item.Value)
            End SyncLock
        End Sub
        Public Sub Clear() Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Clear
            SyncLock SyncRoot
                _Dictionary.Clear()
            End SyncLock
        End Sub
        Public Function Contains(item As KeyValuePair(Of TKey, TValue)) As Boolean Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Contains
            SyncLock SyncRoot
                Return DirectCast(_Dictionary, IDictionary(Of TKey, TValue)).Contains(item)
            End SyncLock
        End Function
        Public Sub CopyTo(array() As KeyValuePair(Of TKey, TValue), arrayIndex As Integer) Implements ICollection(Of KeyValuePair(Of TKey, TValue)).CopyTo
            SyncLock SyncRoot
                DirectCast(_Dictionary, IDictionary(Of TKey, TValue)).CopyTo(array, arrayIndex)
            End SyncLock
        End Sub
        Public ReadOnly Property Count As Integer Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Count
            Get
                SyncLock SyncRoot
                    Return _Dictionary.Count
                End SyncLock
            End Get
        End Property
        Public ReadOnly Property IsReadOnly As Boolean Implements ICollection(Of KeyValuePair(Of TKey, TValue)).IsReadOnly
            Get
                Return False
            End Get
        End Property
        Public Function Remove(item As KeyValuePair(Of TKey, TValue)) As Boolean Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Remove
            SyncLock SyncRoot
                Return DirectCast(_Dictionary, IDictionary(Of TKey, TValue)).Remove(item)
            End SyncLock
        End Function

        '枚举器
        Public Function GetEnumerator() As IEnumerator(Of KeyValuePair(Of TKey, TValue)) Implements IEnumerable(Of KeyValuePair(Of TKey, TValue)).GetEnumerator
            SyncLock SyncRoot
                Return New List(Of KeyValuePair(Of TKey, TValue))(_Dictionary).GetEnumerator()
            End SyncLock
        End Function
        Private Function GetEnumeratorGeneral() As IEnumerator Implements IEnumerable.GetEnumerator
            Return GetEnumerator()
        End Function
    End Class

    ''' <summary>
    ''' 可用于临时存放文件的，不含任何特殊字符的文件夹路径，以“\”结尾。
    ''' </summary>
    Public PathPure As String = GetPureASCIIDir()
    Private Function GetPureASCIIDir() As String
        If ExePath.IsASCII() Then
            Return ExePath & "PCL\"
        ElseIf PathAppdata.IsASCII() Then
            Return PathAppdata
        ElseIf PathTemp.IsASCII() Then
            Return PathTemp
        Else
            Return OsDrive & "ProgramData\PCL\"
        End If
    End Function

    ''' <summary>
    ''' 指示接取到这个异常的函数进行重试。
    ''' </summary>
    Public Class RestartException
        Inherits Exception
    End Class
    ''' <summary>
    ''' 指示用户手动取消了操作，或用户已知晓操作被取消的原因。
    ''' </summary>
    Public Class CancelledException
        Inherits Exception
    End Class

    Public IsRestrictedFeatAllowed As Boolean = False
    ''' <summary>
    ''' 获取区域限制状态，用于判断是否允许使用部分区域限制功能。
    ''' </summary>
    Public Sub GetCoR()
        If TimeZoneInfo.Local.Id = "China Standard Time" AndAlso
            (CultureInfo.CurrentCulture.Name = "zh-CN" OrElse CultureInfo.CurrentUICulture.Name = "zh-CN") Then IsRestrictedFeatAllowed = True
    End Sub

    Private Uuid As Integer = 1
    Private UuidLock As Object
    ''' <summary>
    ''' 获取一个全程序内不会重复的数字（伪 Uuid）。
    ''' </summary>
    Public Function GetUuid() As Integer
        If UuidLock Is Nothing Then UuidLock = New Object
        SyncLock UuidLock
            Uuid += 1
            Return Uuid
        End SyncLock
    End Function

    ''' <summary>
    ''' 将元素与 List 的混合体拆分为元素组。
    ''' </summary>
    Public Function GetFullList(Of T)(data As IList) As List(Of T)
        GetFullList = New List(Of T)
        For i = 0 To data.Count - 1
            If TypeOf data(i) Is ICollection Then
                GetFullList.AddRange(data(i))
            Else
                GetFullList.Add(data(i))
            End If
        Next i
    End Function
    ''' <summary>
    ''' 数组去重。
    ''' </summary>
    <Extension> Public Function Distinct(Of T)(Arr As ICollection(Of T), IsEqual As ComparisonBoolean(Of T)) As List(Of T)
        Dim ResultArray As New List(Of T)
        For i = 0 To Arr.Count - 1
            For ii = i + 1 To Arr.Count - 1
                If IsEqual(Arr(i), Arr(ii)) Then GoTo NextElement
            Next
            ResultArray.Add(Arr(i))
NextElement:
        Next i
        Return ResultArray
    End Function

    ''' <summary>
    ''' 用于储存 RaiseByMouse 的 EventArgs。
    ''' </summary>
    Public NotInheritable Class RouteEventArgs
        Inherits EventArgs
        Public RaiseByMouse As Boolean
        Public Handled As Boolean = False
        Public Sub New(Optional RaiseByMouse As Boolean = False)
            Me.RaiseByMouse = RaiseByMouse
        End Sub
    End Class

    ''' <summary>
    ''' 前台运行文件。
    ''' </summary>
    ''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
    ''' <param name="Arguments">运行参数。</param>
    Public Sub ShellOnly(FileName As String, Optional Arguments As String = "")
        Try
            FileName = ShortenPath(FileName)
            Using Program As New Process
                Program.StartInfo.Arguments = Arguments
                Program.StartInfo.FileName = FileName
                Program.StartInfo.UseShellExecute = True
                Log("[System] 执行外部命令：" & FileName & " " & Arguments)
                Program.Start()
            End Using
        Catch ex As Exception
            Log(ex, "打开文件或程序失败：" & FileName, LogLevel.Msgbox)
        End Try
    End Sub
    ''' <summary>
    ''' 前台运行文件并返回返回值。
    ''' </summary>
    ''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
    ''' <param name="Arguments">运行参数。</param>
    ''' <param name="Timeout">等待该程序结束的最长时间（毫秒）。超时会返回 Result.Timeout。</param>
    Public Function ShellAndGetExitCode(FileName As String, Optional Arguments As String = "", Optional Timeout As Integer = 1000000) As ProcessReturnValues
        Try
            Using Program As New Process
                Program.StartInfo.Arguments = Arguments
                Program.StartInfo.FileName = FileName
                Log("[System] 执行外部命令并等待返回码：" & FileName & " " & Arguments)
                Program.Start()
                If Program.WaitForExit(Timeout) Then
                    Return Program.ExitCode
                Else
                    Return ProcessReturnValues.Timeout
                End If
            End Using
        Catch ex As Exception
            Log(ex, "执行命令失败：" & FileName, LogLevel.Msgbox)
            Return ProcessReturnValues.Fail
        End Try
    End Function
    ''' <summary>
    ''' 静默运行文件并返回输出流字符串。执行失败会抛出异常。
    ''' </summary>
    ''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
    ''' <param name="Arguments">运行参数。</param>
    ''' <param name="Timeout">等待该程序结束的最长时间（毫秒）。超时会抛出错误。</param>
    Public Function ShellAndGetOutput(FileName As String, Optional Arguments As String = "", Optional Timeout As Integer = 1000000, Optional WorkingDirectory As String = Nothing) As String
        Dim Info = New ProcessStartInfo With {
            .FileName = FileName,
            .Arguments = Arguments,
            .UseShellExecute = False,
            .CreateNoWindow = True,
            .RedirectStandardOutput = True,
            .RedirectStandardError = True
        }

        ' 设置工作目录（如果提供）
        If Not String.IsNullOrEmpty(WorkingDirectory) Then
            Info.WorkingDirectory = WorkingDirectory.TrimEnd("\")
        End If

        Log("[System] 执行外部命令并等待返回结果：" & FileName & " " & Arguments)

        Using Program As New Process() With {.StartInfo = Info}
            Program.Start()

            ' 异步读取输出和错误流
            Dim outputTask = Program.StandardOutput.ReadToEndAsync()
            Dim errorTask = Program.StandardError.ReadToEndAsync()

            ' 等待进程退出或超时
            If Program.WaitForExit(Timeout) Then
                ' 确保异步读取完成
                Task.WaitAll(outputTask, errorTask)
            Else
                ' 超时后终止进程
                Program.Kill()
                ' 仍然尝试获取已输出的内容
                Task.WaitAll(outputTask, errorTask)
            End If

            ' 合并结果并返回
            Return outputTask.Result & errorTask.Result
        End Using
    End Function

    ''' <summary>
    ''' 在新的工作线程中执行代码。
    ''' </summary>
    Public Function RunInNewThread(Action As Action, Optional Name As String = Nothing, Optional Priority As ThreadPriority = ThreadPriority.Normal) As Thread
        Dim th As New Thread(
        Sub()
            Try
                Action()
            Catch ex As ThreadInterruptedException
                Log(Name & "：线程已中止")
            Catch ex As Exception
                Log(ex, Name & "：线程执行失败", LogLevel.Feedback)
            End Try
        End Sub) With {.Name = If(Name, "Runtime New Invoke " & GetUuid() & "#"), .Priority = Priority}
        th.Start()
        Return th
    End Function
    ''' <summary>
    ''' 确保在 UI 线程中执行代码。
    ''' 如果当前并非 UI 线程，则会阻断当前线程，直至 UI 线程执行完毕。
    ''' 为防止线程互锁，请仅在开始加载动画、从 UI 获取输入时使用！
    ''' </summary>
    Public Function RunInUiWait(Of Output)(Action As Func(Of Output)) As Output
        If RunInUi() Then
            Return Action()
        Else
            Return Application.Current.Dispatcher.Invoke(Action)
        End If
    End Function
    ''' <summary>
    ''' 确保在 UI 线程中执行代码。
    ''' 如果当前并非 UI 线程，则会阻断当前线程，直至 UI 线程执行完毕。
    ''' 为防止线程互锁，请仅在开始加载动画、从 UI 获取输入时使用！
    ''' </summary>
    Public Sub RunInUiWait(Action As Action)
        If RunInUi() Then
            Action()
        Else
            Application.Current.Dispatcher.Invoke(Action)
        End If
    End Sub
    ''' <summary>
    ''' 确保在 UI 线程中执行代码，代码按触发顺序执行。
    ''' 如果当前并非 UI 线程，也不阻断当前线程的执行。
    ''' </summary>
    Public Sub RunInUi(Action As Action, Optional ForceWaitUntilLoaded As Boolean = False)
        If ForceWaitUntilLoaded Then
            Application.Current.Dispatcher.InvokeAsync(Action, Threading.DispatcherPriority.Loaded)
        ElseIf RunInUi() Then
            Action()
        Else
            Application.Current.Dispatcher.InvokeAsync(Action)
        End If
    End Sub
    ''' <summary>
    ''' 确保在工作线程中执行代码。
    ''' </summary>
    Public Sub RunInThread(Action As Action)
        If RunInUi() Then
            RunInNewThread(Action, "Runtime Invoke " & GetUuid() & "#")
        Else
            Action()
        End If
    End Sub

    ''' <summary>
    ''' 使用优化的归并排序算法进行稳定排序。
    ''' </summary>
    ''' <param name="SortRule">传入两个对象，若第一个对象应该排在前面，则返回 True。</param>
    <Extension>
    Public Function Sort(Of T)(List As IList(Of T), SortRule As ComparisonBoolean(Of T)) As List(Of T)
        ' 创建原列表的副本以避免修改原始列表
        Dim tempList As New List(Of T)(List)
        If tempList.Count <= 1 Then Return tempList

        ' 使用归并排序核心算法
        MergeSort_Sort(tempList, 0, tempList.Count - 1, SortRule)
        Return tempList
    End Function

    Private Sub MergeSort_Sort(Of T)(ByRef array As List(Of T), left As Integer, right As Integer, comparator As ComparisonBoolean(Of T))
        If left >= right Then Return

        Dim mid As Integer = (left + right) \ 2
        MergeSort_Sort(array, left, mid, comparator)
        MergeSort_Sort(array, mid + 1, right, comparator)
        MergeSort_Merge(array, left, mid, right, comparator)
    End Sub

    Private Sub MergeSort_Merge(Of T)(ByRef array As List(Of T), left As Integer, mid As Integer, right As Integer, comparator As ComparisonBoolean(Of T))
        Dim leftArray As New List(Of T)
        Dim rightArray As New List(Of T)

        For i As Integer = left To mid
            leftArray.Add(array(i))
        Next

        For j As Integer = mid + 1 To right
            rightArray.Add(array(j))
        Next

        Dim leftPtr = 0, rightPtr = 0, current = left

        While leftPtr < leftArray.Count AndAlso rightPtr < rightArray.Count
            ' 保持稳定性的关键比较逻辑：当相等时优先取左数组元素
            If comparator(leftArray(leftPtr), rightArray(rightPtr)) Then
                array(current) = leftArray(leftPtr)
                leftPtr += 1
            Else
                array(current) = rightArray(rightPtr)
                rightPtr += 1
            End If
            current += 1
        End While

        While leftPtr < leftArray.Count
            array(current) = leftArray(leftPtr)
            leftPtr += 1
            current += 1
        End While

        While rightPtr < rightArray.Count
            array(current) = rightArray(rightPtr)
            rightPtr += 1
            current += 1
        End While
    End Sub

    Public Delegate Function ComparisonBoolean(Of T)(Left As T, Right As T) As Boolean

    ''' <summary>
    ''' 返回列表的浅表副本。
    ''' </summary>
    <Extension> Public Function Clone(Of T)(list As IList(Of T)) As IList(Of T)
        Return New List(Of T)(list)
    End Function

    ''' <summary>
    ''' 尝试从字典中获取某项，如果该项不存在，则返回默认值。
    ''' </summary>
    <Extension> Public Function GetOrDefault(Of TKey, TValue)(Dict As Dictionary(Of TKey, TValue), Key As TKey, Optional DefaultValue As TValue = Nothing) As TValue
        If Dict.ContainsKey(Key) Then
            Return Dict(Key)
        Else
            Return DefaultValue
        End If
    End Function
    ''' <summary>
    ''' 将某项添加到以列表作为值的字典中。
    ''' </summary>
    <Extension> Public Sub AddToList(Of TKey, TValue)(Dict As Dictionary(Of TKey, List(Of TValue)), Key As TKey, Value As TValue)
        If Dict.ContainsKey(Key) Then
            Dict(Key).Add(Value)
        Else
            Dict.Add(Key, New List(Of TValue) From {Value})
        End If
    End Sub

    ''' <summary>
    ''' 获取程序启动参数。
    ''' </summary>
    ''' <param name="Name">参数名。</param>
    ''' <param name="DefaultValue">默认值。</param>
    Public Function GetProgramArgument(Name As String, Optional DefaultValue As Object = "")
        Dim AllArguments() As String = Command.Split(" ")
        For i = 0 To AllArguments.Length - 1
            If AllArguments(i) = "-" & Name Then
                If AllArguments.Length = i + 1 OrElse AllArguments(i + 1).StartsWithF("-") Then Return True
                Return AllArguments(i + 1)
            End If
        Next
        Return DefaultValue
    End Function

    ''' <summary>
    ''' 打开网页。
    ''' </summary>
    Public Sub OpenWebsite(Url As String)
        Try
            If Not Url.StartsWithF("http", True) AndAlso Not Url.StartsWithF("minecraft://", True) Then
                Throw New Exception(Url & " 不是一个有效的网址，它必须以 http 开头！")
            End If
            Log("[System] 正在打开网页：" & Url)
            Basics.OpenPath(Url)
        Catch ex As Exception
            Log(ex, "无法打开网页（" & Url & "）")
            ClipboardSet(Url, False)
            MyMsgBox("可能由于浏览器未正确配置，PCL 无法为你打开网页。" & vbCrLf & "网址已经复制到剪贴板，若有需要可以手动粘贴访问。" & vbCrLf &
                     $"网址：{Url}", "无法打开网页")
        End Try
    End Sub
    ''' <summary>
    ''' 打开 explorer。
    ''' 若不以 \ 结尾，则将视作文件路径，打开并选中此文件。
    ''' </summary>
    Public Sub OpenExplorer(Location As String)
        Try
            Location = ShortenPath(Location.Replace("/", "\").Trim(" "c, """"c))
            Log("[System] 正在打开资源管理器：" & Location)
            If Location.EndsWithF("\") Then
                ShellOnly(Location)
            Else
                ShellOnly("explorer", $"/select,""{Location}""")
            End If
        Catch ex As Exception
            Log(ex, "打开资源管理器失败，请尝试关闭安全软件（如 360 安全卫士）", LogLevel.Msgbox)
        End Try
    End Sub

    ''' <summary>
    ''' 设置剪贴板。将在另一线程运行，且不会抛出异常。
    ''' </summary>
    Public Sub ClipboardSet(Text As String, Optional ShowSuccessHint As Boolean = True)
        RunInThread(Sub()
                        Dim success As Boolean = False

                        For attempt As Integer = 0 To 5
                            Try
                                RunInUi(Sub()
                                            Clipboard.SetText(Text)
                                        End Sub)
                                success = True
                                Exit For
                            Catch ex As Exception When attempt < 5
                                Thread.Sleep(20)
                            Catch finalEx As Exception
                                Log(finalEx, "剪贴板被占用，文本复制失败", LogLevel.Hint)
                            End Try
                        Next

                        If success AndAlso ShowSuccessHint Then
                            RunInUi(Sub() Hint("已成功复制！", HintType.Finish))
                        End If
                    End Sub)
    End Sub

    ''' <summary>
    ''' 从剪切板粘贴文件或文件夹
    ''' </summary>
    ''' <param name="dest">目标文件夹</param>
    ''' <param name="copyFile">是否粘贴文件</param>
    ''' <param name="copyDir">是否粘贴文件夹</param>
    ''' <returns>总共粘贴的数量</returns>
    Public Function PasteFileFromClipboard(dest As String, Optional copyFile As Boolean = True, Optional copyDir As Boolean = True) As Integer
        Log("[System] 从剪贴板粘贴文件到：" & dest)
        Try
            Dim files As Specialized.StringCollection = Clipboard.GetFileDropList()
            If files.Count.Equals(0) Then
                Log("[System] 剪贴板内无文件可粘贴")
                Return 0
            End If
            Dim CopiedFiles = 0
            Dim CopiedFolders = 0
            For Each i In files
                If copyFile AndAlso File.Exists(i) Then '文件
                    Try
                        Dim thisDest = dest & GetFileNameFromPath(i)
                        If File.Exists(thisDest) Then
                            Log("[System] 已存在同名文件：" & thisDest)
                        Else
                            File.Copy(i, thisDest)
                            CopiedFiles += 1
                        End If
                    Catch ex As Exception
                        Log(ex, "[System] 复制文件时出错")
                        Continue For
                    End Try
                End If
                If copyDir AndAlso Directory.Exists(i) Then '文件夹
                    Try
                        Dim thisDest = dest & GetFolderNameFromPath(i)
                        If Directory.Exists(thisDest) Then
                            Log("[System] 已存在同名文件夹：" & thisDest)
                        Else
                            CopyDirectory(i, thisDest)
                            CopiedFolders += 1
                        End If
                    Catch ex As Exception
                        Log(ex, "[System] 复制文件时出错")
                        Continue For
                    End Try
                End If
            Next
            Hint("[System] 已粘贴 " & CopiedFiles & " 个文件和 " & CopiedFolders & " 个文件夹")
        Catch ex As Exception
            Log(ex, "[System] 从剪切板粘贴文件失败", LogLevel.Hint)
        End Try
        Return 0
    End Function

    ''' <summary>
    ''' 获取程序打包资源的输入流。该资源必须声明为 <c>Resource</c> 类型，否则将会报错，<c>Images</c>
    ''' 和 <c>Resources</c> 目录已默认声明该类型。
    ''' </summary>
    Public Function GetResourceStream(path As String) As Stream
        Dim resourceInfo = Application.GetResourceStream(New Uri($"pack://application:,,,/{path}", UriKind.Absolute))
        Return resourceInfo?.Stream
    End Function

#End Region
    
    ''' 检查是否拥有某一文件夹的 I/O 权限。如果文件夹不存在，会返回 False。
    ''' </summary>
    Public Function CheckPermission(Path As String) As Boolean
        Try
            If String.IsNullOrEmpty(Path) Then Return False
            If Not Path.EndsWithF("\") Then Path += "\"
            If Path.EndsWithF(":\System Volume Information\") OrElse Path.EndsWithF(":\$RECYCLE.BIN\") Then Return False
            If Not Directory.Exists(Path) Then Return False
            Dim FileName As String = "CheckPermission" & GetUuid()
            If File.Exists(Path & FileName) Then File.Delete(Path & FileName)
            File.Create(Path & FileName).Dispose()
            File.Delete(Path & FileName)
            Return True
        Catch ex As Exception
            Log(ex, "没有对文件夹 " & Path & " 的权限，请尝试以管理员权限运行 PCL")
            Return False
        End Try
    End Function
    ''' <summary>
    ''' 检查是否拥有某一文件夹的 I/O 权限。如果出错，则抛出异常。
    ''' </summary>
    Public Sub CheckPermissionWithException(Path As String)
        If String.IsNullOrWhiteSpace(Path) Then Throw New ArgumentNullException("文件夹名不能为空！")
        If Not Path.EndsWithF("\") Then Path += "\"
        If Not Directory.Exists(Path) Then Throw New DirectoryNotFoundException("文件夹不存在！")
        If File.Exists(Path & "CheckPermission") Then File.Delete(Path & "CheckPermission")
        File.Create(Path & "CheckPermission").Dispose()
        File.Delete(Path & "CheckPermission")
    End Sub
    ''' <summary>

#Region "UI"

    Public Sub SetLaunchFont(Optional FontName As String = Nothing)
        Try
            Dim TargetFont As FontFamily
            If String.IsNullOrEmpty(FontName) Then
                TargetFont = New FontFamily(New Uri("pack://application:,,,/"), "./Resources/#PCL English, Segoe UI, Microsoft YaHei UI")
            Else
                TargetFont = New FontFamily($"{FontName}, Segoe UI, Microsoft YaHei UI")
            End If
            Application.Current.Resources("LaunchFontFamily") = TargetFont
        Catch ex As Exception
            Log(ex, "设置字体失败", LogLevel.Hint)
        End Try
    End Sub

    '边距改变
    ''' <summary>
    ''' 相对增减控件的左边距。
    ''' </summary>
    Public Sub DeltaLeft(control As FrameworkElement, newValue As Double)
        '安全性检查
        DebugAssert(Not Double.IsNaN(newValue))
        DebugAssert(Not Double.IsInfinity(newValue))

        If TypeOf control Is Window Then
            '窗口改变
            CType(control, Window).Left += newValue
        Else
            '根据 HorizontalAlignment 改变数值
            Select Case control.HorizontalAlignment
                Case HorizontalAlignment.Left, HorizontalAlignment.Stretch
                    control.Margin = New Thickness(control.Margin.Left + newValue, control.Margin.Top, control.Margin.Right, control.Margin.Bottom)
                Case HorizontalAlignment.Right
                    control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right - newValue, control.Margin.Bottom)
                    'control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, CType(control.Parent, Object).ActualWidth - control.ActualWidth - newValue, control.Margin.Bottom)
                Case Else
                    DebugAssert(False)
            End Select
        End If
    End Sub
    ''' <summary>
    ''' 设置控件的左边距。（仅针对置左控件）
    ''' </summary>
    Public Sub SetLeft(control As FrameworkElement, newValue As Double)
        DebugAssert(control.HorizontalAlignment = HorizontalAlignment.Left)
        control.Margin = New Thickness(newValue, control.Margin.Top, control.Margin.Right, control.Margin.Bottom)
    End Sub
    ''' <summary>
    ''' 相对增减控件的上边距。
    ''' </summary>
    Public Sub DeltaTop(control As FrameworkElement, newValue As Double)
        '安全性检查
        DebugAssert(Not Double.IsNaN(newValue))
        DebugAssert(Not Double.IsInfinity(newValue))

        If TypeOf control Is Window Then
            '窗口改变
            CType(control, Window).Top += newValue
        Else
            '根据 VerticalAlignment 改变数值
            Select Case control.VerticalAlignment
                Case VerticalAlignment.Top
                    control.Margin = New Thickness(control.Margin.Left, control.Margin.Top + newValue, control.Margin.Right, control.Margin.Bottom)
                Case VerticalAlignment.Bottom
                    control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right, control.Margin.Bottom - newValue)
                    'control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, CType(control.Parent, Object).ActualWidth - control.ActualWidth - newValue, control.Margin.Bottom)
                Case Else
                    DebugAssert(False)
            End Select
        End If

        'If Double.IsNaN(newValue) OrElse Double.IsInfinity(newValue) Then Return '安全性检查
        'Select Case control.VerticalAlignment
        '  Case VerticalAlignment.Top, VerticalAlignment.Stretch, VerticalAlignment.Center
        '      control.Margin = New Thickness(control.Margin.Left, newValue, control.Margin.Right, control.Margin.Bottom)
        '  Case VerticalAlignment.Bottom
        '      control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right, -newValue)
        '      'control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right, CType(control.Parent, Object).ActualHeight - control.ActualHeight - newValue)
        'End Select
    End Sub
    ''' <summary>
    ''' 设置控件的顶边距。（仅针对置上控件）
    ''' </summary>
    Public Sub SetTop(control As FrameworkElement, newValue As Double)
        DebugAssert(control.VerticalAlignment = VerticalAlignment.Top)
        control.Margin = New Thickness(control.Margin.Left, newValue, control.Margin.Right, control.Margin.Bottom)
    End Sub

    'DPI 转换
    Public ReadOnly DPI As Integer = System.Drawing.Graphics.FromHwnd(IntPtr.Zero).DpiX
    ''' <summary>
    ''' 将经过 DPI 缩放的 WPF 尺寸转化为实际的像素尺寸。
    ''' </summary>
    Public Function GetPixelSize(WPFSize As Double) As Double
        Return WPFSize / 96 * DPI
    End Function
    ''' <summary>
    ''' 将实际的像素尺寸转化为经过 DPI 缩放的 WPF 尺寸。
    ''' </summary>
    Public Function GetWPFSize(PixelSize As Double) As Double
        Return PixelSize * 96 / DPI
    End Function

    'UI 截图
    ''' <summary>
    ''' 将某个控件的呈现转换为图片。
    ''' </summary>
    Public Function ControlBrush(UI As FrameworkElement) As ImageBrush
        Dim Width = UI.ActualWidth, Height = UI.ActualHeight
        If Width < 1 OrElse Height < 1 Then Return New ImageBrush
        Dim bmp As New RenderTargetBitmap(GetPixelSize(Width), GetPixelSize(Height), DPI, DPI, PixelFormats.Pbgra32)
        bmp.Render(UI)
        Return New ImageBrush(bmp)
    End Function
    ''' <summary>
    ''' 将某个控件的模拟呈现转换为图片。
    ''' </summary>
    Public Function ControlBrush(UI As FrameworkElement, Width As Double, Height As Double, Optional Left As Double = 0, Optional Top As Double = 0) As ImageBrush
        UI.Measure(New Size(Width, Height))
        UI.Arrange(New Rect(0, 0, Width, Height))
        Dim bmp As New RenderTargetBitmap(GetPixelSize(Width), GetPixelSize(Height), DPI, DPI, PixelFormats.Default)
        bmp.Render(UI)
        If Not (Left = 0 AndAlso Top = 0) Then UI.Arrange(New Rect(Left, Top, Width, Height))
        Return New ImageBrush(bmp)
    End Function
    ''' <summary>
    ''' 将 UI 内容固定为图片并进行 Clear。
    ''' </summary>
    Public Sub ControlFreeze(UI As Panel)
        UI.Background = ControlBrush(UI)
        UI.Children.Clear()
    End Sub
    ''' <summary>
    ''' 将 UI 内容固定为图片并进行 Clear。
    ''' </summary>
    Public Sub ControlFreeze(UI As Border)
        UI.Background = ControlBrush(UI)
        UI.Child = Nothing
    End Sub

    ''' <summary>
    ''' 将 XML 转换为对应 UI 对象。
    ''' </summary>
    Public Function GetObjectFromXML(Str As XElement)
        Return GetObjectFromXML(Str.ToString)
    End Function
    ''' <summary>
    ''' 将 XML 转换为对应 UI 对象。
    ''' </summary>
    Public Function GetObjectFromXML(Str As String) As Object
        Using Stream As New MemoryStream(Encoding.UTF8.GetBytes(Str))
            '类型检查
            Using Reader As New XamlXmlReader(Stream)
                While Reader.Read()
                    For Each BlackListType In {GetType(WebBrowser), GetType(Frame), GetType(MediaElement), GetType(ObjectDataProvider), GetType(XamlReader), GetType(Window), GetType(XmlDataProvider)}
                        If Reader.Type IsNot Nothing AndAlso BlackListType.IsAssignableFrom(Reader.Type.UnderlyingType) Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListType.Name} 类型。")
                        If Reader.Value IsNot Nothing AndAlso Reader.Value = BlackListType.Name Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListType.Name} 值。")
                    Next
                    For Each BlackListMember In {"Code", "FactoryMethod", "Static"}
                        If Reader.Member IsNot Nothing AndAlso Reader.Member.Name = BlackListMember Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListMember} 成员。")
                    Next
                End While
            End Using
            '实际的加载
            Stream.Position = 0
            Using Writer As New StreamWriter(Stream)
                Writer.Write(Str)
                Writer.Flush()
                Stream.Position = 0
                Return Markup.XamlReader.Load(Stream)
            End Using
        End Using
    End Function

    Private ReadOnly UiThreadId As Integer = Thread.CurrentThread.ManagedThreadId
    ''' <summary>
    ''' 当前线程是否为主线程。
    ''' </summary>
    Public Function RunInUi() As Boolean
        Return Thread.CurrentThread.ManagedThreadId = UiThreadId
    End Function

#End Region

#Region "Debug"

    Public ModeDebug As Boolean = False

    'Log
    Public Enum LogLevel
        ''' <summary>
        ''' 不提示，只记录日志。
        ''' </summary>
        Normal = 0
        ''' <summary>
        ''' 只提示开发者。
        ''' </summary>
        Developer = 1
        ''' <summary>
        ''' 只提示开发者与调试模式用户。
        ''' </summary>
        Debug = 2
        ''' <summary>
        ''' 弹出提示所有用户。
        ''' </summary>
        Hint = 3
        ''' <summary>
        ''' 弹窗，不要求反馈。
        ''' </summary>
        Msgbox = 4
        ''' <summary>
        ''' 弹窗，要求反馈。
        ''' </summary>
        Feedback = 5
        ''' <summary>
        ''' 弹出 Windows 原生弹窗，要求反馈。在无法保证 WPF 窗口能正常运行时使用此级别。
        ''' 在第二次触发后会直接结束程序。
        ''' </summary>
        Critical = 6
    End Enum
    Private IsCriticalErrorTriggered As Boolean = False
    ''' <summary>
    ''' 输出 Log。
    ''' </summary>
    ''' <param name="Title">如果要求弹窗，指定弹窗的标题。</param>
    Public Sub Log(Text As String, Optional Level As LogLevel = LogLevel.Normal, Optional Title As String = "出现错误")
        On Error Resume Next
        '放在最后会导致无法显示极端错误下的弹窗（如无法写入日志文件）
        '处理错误会导致再次调用 Log() 导致无限循环

        '输出日志
        If {LogLevel.Msgbox, LogLevel.Hint}.Contains(Level) Then
            LogWrapper.Warn(Text)
        ElseIf LogLevel.Feedback = Level Then
            LogWrapper.Error(Text)
        ElseIf LogLevel.Critical = Level Then
            LogWrapper.Fatal(Text)
        ElseIf LogLevel.Debug = Level Then
            LogWrapper.Debug(Text)
        ElseIf LogLevel.Developer = Level Then
            LogWrapper.Trace(Text)
        Else
            LogWrapper.Info(Text)
        End If

        If IsProgramEnded OrElse Level = LogLevel.Normal Then Return

        '去除前缀
        Text = Text.RegexReplace("\[[^\]]+?\] ", "")

        '输出提示
        Select Case Level
#If DEBUGRESERVED Then
            Case LogLevel.Developer
                Hint("[开发者模式] " & Text, HintType.Info, False)
            Case LogLevel.Debug
                Hint("[调试模式] " & Text, HintType.Info, False)
#Else
            Case LogLevel.Developer
            Case LogLevel.Debug
                If ModeDebug Then Hint("[调试模式] " & Text, HintType.Info, False)
#End If
            Case LogLevel.Hint
                Hint(Text, HintType.Critical, False)
            Case LogLevel.Msgbox
                MyMsgBox(Text, Title, IsWarn:=True)
            Case LogLevel.Feedback
                If CanFeedback(False) Then
                    If MyMsgBox(Text & vbCrLf & vbCrLf & "是否反馈此问题？如果不反馈，这个问题可能永远无法得到解决！", Title, "反馈", "取消", IsWarn:=True) = 1 Then Feedback(False, True)
                Else
                    MyMsgBox(Text & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", Title, IsWarn:=True)
                End If
            Case LogLevel.Critical
                If IsCriticalErrorTriggered Then
                    FormMain.EndProgramForce(ProcessReturnValues.Exception)
                    Return
                End If
                IsCriticalErrorTriggered = True
                If CanFeedback(False) Then
                    If MsgBox(Text & vbCrLf & vbCrLf & "是否反馈此问题？如果不反馈，这个问题可能永远无法得到解决！", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, Title) = MsgBoxResult.Yes Then Feedback(False, True)
                Else
                    MsgBox(Text & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", MsgBoxStyle.Critical, Title)
                End If
        End Select

    End Sub
    ''' <summary>
    ''' 输出错误信息。
    ''' </summary>
    ''' <param name="Desc">错误描述。会在处理时在末尾加入冒号。</param>
    Public Sub Log(Ex As Exception, Desc As String, Optional Level As LogLevel = LogLevel.Debug, Optional Title As String = "出现错误")
        On Error Resume Next
        If TypeOf Ex Is ThreadInterruptedException Then Return

        '获取错误信息
        Dim ExFull As String = Desc & "：" & Ex.Message

        '输出日志
        If {LogLevel.Msgbox, LogLevel.Hint}.Contains(Level) Then
            LogWrapper.Warn(Ex, Desc)
        ElseIf LogLevel.Feedback = Level Then
            LogWrapper.Error(Ex, Desc)
        ElseIf LogLevel.Critical = Level Then
            LogWrapper.Fatal(Ex, Desc)
        ElseIf LogLevel.Debug = Level Then
            LogWrapper.Debug($"{Desc}:{Ex}")
        ElseIf LogLevel.Developer = Level Then
            LogWrapper.Trace($"{Desc}:{Ex}")
        Else
            LogWrapper.Error(Ex, Desc)
        End If

        If IsProgramEnded Then Return

        If Ex.GetType() = GetType(ComponentModel.Win32Exception) Then ExFull += vbCrLf & "与系统底层交互失败，请尝试重新安装 .NET 8 解决此问题"

        '输出提示
        Select Case Level
            Case LogLevel.Normal
#If DEBUGRESERVED Then
            Case LogLevel.Developer
                Dim ExLine As String = Desc & "：" & Ex.ToString()
                Hint("[开发者模式] " & ExLine, HintType.Info, False)
            Case LogLevel.Debug
                Dim ExLine As String = Desc & "：" & Ex.ToString()
                Hint("[调试模式] " & ExLine, HintType.Info, False)
#Else
            Case LogLevel.Developer
            Case LogLevel.Debug
                Dim ExLine As String = Desc & "：" & Ex.ToString()
                If ModeDebug Then Hint("[调试模式] " & ExLine, HintType.Info, False)
#End If
            Case LogLevel.Hint
                Dim ExLine As String = Desc & "：" & Ex.ToString()
                Hint(ExLine, HintType.Critical, False)
            Case LogLevel.Msgbox
                MyMsgBox(ExFull, Title, IsWarn:=True)
            Case LogLevel.Feedback
                If CanFeedback(False) Then
                    If MyMsgBox(ExFull & vbCrLf & vbCrLf & "是否反馈此问题？如果不反馈，这个问题可能永远无法得到解决！", Title, "反馈", "取消", IsWarn:=True) = 1 Then Feedback(False, True)
                Else
                    MyMsgBox(ExFull & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", Title, IsWarn:=True)
                End If
            Case LogLevel.Critical
                If IsCriticalErrorTriggered Then
                    FormMain.EndProgramForce(ProcessReturnValues.Exception)
                    Return
                End If
                IsCriticalErrorTriggered = True
                If CanFeedback(False) Then
                    If MsgBox(ExFull & vbCrLf & vbCrLf & "是否反馈此问题？如果不反馈，这个问题可能永远无法得到解决！", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, Title) = MsgBoxResult.Yes Then Feedback(False, True)
                Else
                    MsgBox(ExFull & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", MsgBoxStyle.Critical, Title)
                End If
        End Select

    End Sub
    Public Function Base64Decode(Text As String) As String
        If String.IsNullOrWhiteSpace(Text) Then Return ""
        Dim decodedBytes As Byte() = Convert.FromBase64String(Text)
        Return System.Text.Encoding.UTF8.GetString(decodedBytes)
    End Function
    Public Function Base64Encode(Text As String) As String
        Dim bytes As Byte() = System.Text.Encoding.UTF8.GetBytes(Text)
        Return Convert.ToBase64String(bytes)
    End Function
    Public Function Base64Encode(bytes As Byte()) As String
        Return Convert.ToBase64String(bytes)
    End Function
    '反馈
    Public Sub Feedback(Optional ShowMsgbox As Boolean = True, Optional ForceOpenLog As Boolean = False)
        On Error Resume Next
        FeedbackInfo()
        Dim currentDate As String
        currentDate = Format(Now, "yyyy-M-dd")

        If ForceOpenLog OrElse (ShowMsgbox AndAlso MyMsgBox("若你在汇报一个 Bug，请点击 打开文件夹 按钮，并上传 Launch-" & currentDate & "-[一串数字].log 中包含错误信息的文件。" & vbCrLf & "游戏崩溃一般与启动器无关，请不要因为游戏崩溃而提交反馈。", "反馈提交提醒", "打开文件夹", "不需要") = 1) Then
            OpenExplorer(ExePath & "PCL\Log\")
        End If
        OpenWebsite("https://github.com/PCL-Community/PCL2-CE/issues/")
    End Sub
    Public Function CanFeedback(ShowHint As Boolean) As Boolean
        If Not IsVerisonLatest() Then
            If ShowHint Then
                If MyMsgBox($"你的 PCL 不是最新版，因此无法提交反馈。{vbCrLf}请在更新后，确认该问题在最新版中依然存在，然后再提交反馈。", "无法提交反馈", "更新", "取消") = 1 Then
                    UpdateCheckByButton()
                End If
            End If
            Return False
        Else
            Return True
        End If
    End Function
    ''' <summary>
    ''' 在日志中输出系统诊断信息。
    ''' </summary>
    Public Sub FeedbackInfo()
        On Error Resume Next
        Dim phyRam = KernelInterop.GetPhysicalMemoryBytes()
        Log("[System] 诊断信息：" & vbCrLf &
            "操作系统：" & RuntimeInformation.OSDescription & "（32 位：" & Is32BitSystem & "）" & vbCrLf &
            "剩余内存：" & Int(phyRam.Available / 1024 / 1024) & " M / " & Int(phyRam.Total / 1024 / 1024) & " M" & vbCrLf &
            "DPI：" & DPI & "（" & Math.Round(DPI / 96, 2) * 100 & "%）" & vbCrLf &
            "MC 文件夹：" & If(PathMcFolder, "Nothing") & vbCrLf &
            "文件位置：" & ExePath)
    End Sub

    '断言
    Public Sub DebugAssert(Exp As Boolean)
        If Not Exp Then Throw New Exception("断言命中")
    End Sub

    '获取当前的堆栈信息
    Public Function GetStackTrace() As String
        Dim Stack As New StackTrace()
        Return Join(Stack.GetFrames().Skip(1).Select(Function(f) f.GetMethod).
                    Select(Function(f) f.Name & "(" & Join(f.GetParameters.Select(Function(p) p.ToString).ToList, ", ") & ") - " & f.Module.ToString).ToList,
                    vbCrLf).Replace(vbCrLf & vbCrLf, vbCrLf)
    End Function

#End Region

End Module

#Region "WPF"

''' <summary>
''' 对数据绑定进行加法运算，使用参数决定加数。
''' </summary>
Public Class AdditionConverter
    Implements IValueConverter
    Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
        If value Is Nothing Then Return 0
        Dim before As Double
        If Not Double.TryParse(value.ToString(), before) Then Return 0
        Dim scale As Double = 1
        If parameter IsNot Nothing Then Double.TryParse(parameter.ToString(), scale)
        Return before + scale
    End Function
    Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
        If value Is Nothing Then Return Binding.DoNothing
        Dim before As Double
        If Not Double.TryParse(value.ToString(), before) Then Return Binding.DoNothing
        Dim scale As Double = 1
        If parameter IsNot Nothing Then Double.TryParse(parameter.ToString(), scale)
        If scale = 0 Then Return Binding.DoNothing
        Return before - scale
    End Function
End Class

''' <summary>
''' 对数据绑定进行乘法运算，使用参数决定乘数。
''' </summary>
Public Class MultiplicationConverter
    Implements IValueConverter
    Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
        If value Is Nothing Then Return 0
        Dim before As Double
        If Not Double.TryParse(value.ToString(), before) Then Return 0
        Dim scale As Double = 1
        If parameter IsNot Nothing Then Double.TryParse(parameter.ToString(), scale)
        Return before * scale
    End Function
    Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
        If value Is Nothing Then Return Binding.DoNothing
        Dim before As Double
        If Not Double.TryParse(value.ToString(), before) Then Return Binding.DoNothing
        Dim scale As Double = 1
        If parameter IsNot Nothing Then Double.TryParse(parameter.ToString(), scale)
        If scale = 0 Then Return Binding.DoNothing
        Return before / scale
    End Function
End Class

''' <summary>
''' 将取反的 Boolean 绑定到 Visibility。
''' </summary>
Public Class InverseBooleanToVisibilityConverter
    Implements IValueConverter
    Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
        If value Is Nothing Then Return Visibility.Visible
        Dim boolValue As Boolean
        Return If(Boolean.TryParse(value.ToString(), boolValue), If(boolValue, Visibility.Collapsed, Visibility.Visible), Visibility.Visible)
    End Function
    Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
        If value Is Nothing Then Return False
        Return If(TypeOf value Is Visibility, value <> Visibility.Visible, False)
    End Function
End Class

''' <summary>
''' 将 Boolean 取反。
''' </summary>
Public Class InverseBooleanConverter
    Implements IValueConverter
    Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
        If value Is Nothing Then Return False
        Dim boolValue As Boolean
        Return If(Boolean.TryParse(value.ToString(), boolValue), Not boolValue, False)
    End Function
    Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
        If value Is Nothing Then Return False
        Return If(Boolean.TryParse(value.ToString(), value), Not value, False)
    End Function
End Class

#End Region
