Sfoglia il codice sorgente

添加项目文件。

LTCatt 5 anni fa
parent
commit
5a929d2aa2
100 ha cambiato i file con 9236 aggiunte e 0 eliminazioni
  1. 79 0
      Plain Craft Launcher Admin Manager/ApplicationEvents.vb
  2. 13 0
      Plain Craft Launcher Admin Manager/My Project/Application.Designer.vb
  3. 9 0
      Plain Craft Launcher Admin Manager/My Project/Application.myapp
  4. 35 0
      Plain Craft Launcher Admin Manager/My Project/AssemblyInfo.vb
  5. 63 0
      Plain Craft Launcher Admin Manager/My Project/Resources.Designer.vb
  6. 117 0
      Plain Craft Launcher Admin Manager/My Project/Resources.resx
  7. 73 0
      Plain Craft Launcher Admin Manager/My Project/Settings.Designer.vb
  8. 7 0
      Plain Craft Launcher Admin Manager/My Project/Settings.settings
  9. 48 0
      Plain Craft Launcher Admin Manager/My Project/app.manifest
  10. 128 0
      Plain Craft Launcher Admin Manager/Plain Craft Launcher Admin Manager.vbproj
  11. 31 0
      Plain Craft Launcher.sln
  12. 48 0
      Plain Craft Launcher.vshost.exe.manifest
  13. 222 0
      Plain Craft Launcher.xml
  14. 70 0
      Plain Craft Launcher/Application.xaml
  15. 52 0
      Plain Craft Launcher/Application.xaml.vb
  16. 10 0
      Plain Craft Launcher/Base/Button.xaml
  17. 111 0
      Plain Craft Launcher/Base/Button.xaml.vb
  18. 23 0
      Plain Craft Launcher/Base/Checkbox.xaml
  19. 134 0
      Plain Craft Launcher/Base/Checkbox.xaml.vb
  20. 62 0
      Plain Craft Launcher/Base/InfoBox.xaml
  21. 243 0
      Plain Craft Launcher/Base/InfoBox.xaml.vb
  22. 22 0
      Plain Craft Launcher/Base/ListItem.xaml
  23. 221 0
      Plain Craft Launcher/Base/ListItem.xaml.vb
  24. 205 0
      Plain Craft Launcher/Base/Loading.xaml
  25. 3 0
      Plain Craft Launcher/Base/Loading.xaml.vb
  26. 25 0
      Plain Craft Launcher/Base/MetroMsgbox.xaml
  27. 97 0
      Plain Craft Launcher/Base/MetroMsgbox.xaml.vb
  28. 3957 0
      Plain Craft Launcher/Base/Modules.vb
  29. 17 0
      Plain Craft Launcher/Base/Radiobox.xaml
  30. 146 0
      Plain Craft Launcher/Base/Radiobox.xaml.vb
  31. 11 0
      Plain Craft Launcher/Base/RightList.xaml
  32. 3 0
      Plain Craft Launcher/Base/RightList.xaml.vb
  33. 9 0
      Plain Craft Launcher/Base/RoundProcessbar.xaml
  34. 117 0
      Plain Craft Launcher/Base/RoundProcessbar.xaml.vb
  35. 12 0
      Plain Craft Launcher/Base/Selecter.xaml
  36. 106 0
      Plain Craft Launcher/Base/Selecter.xaml.vb
  37. 16 0
      Plain Craft Launcher/Base/Timer.xaml
  38. 72 0
      Plain Craft Launcher/Base/Timer.xaml.vb
  39. 12 0
      Plain Craft Launcher/Base/VScroll.xaml
  40. 295 0
      Plain Craft Launcher/Base/VScroll.xaml.vb
  41. BIN
      Plain Craft Launcher/Images/Back.png
  42. BIN
      Plain Craft Launcher/Images/Block-Add.png
  43. BIN
      Plain Craft Launcher/Images/Block-Anvil.png
  44. BIN
      Plain Craft Launcher/Images/Block-CobbleStone.png
  45. BIN
      Plain Craft Launcher/Images/Block-CommandBlock.png
  46. BIN
      Plain Craft Launcher/Images/Block-Dirt.png
  47. BIN
      Plain Craft Launcher/Images/Block-False.png
  48. BIN
      Plain Craft Launcher/Images/Block-Glass.png
  49. BIN
      Plain Craft Launcher/Images/Block-Grass.png
  50. BIN
      Plain Craft Launcher/Images/Block-RedstoneBlock.png
  51. BIN
      Plain Craft Launcher/Images/Block-Settings.png
  52. BIN
      Plain Craft Launcher/Images/Block-True.png
  53. BIN
      Plain Craft Launcher/Images/Checked-Black.png
  54. BIN
      Plain Craft Launcher/Images/Checked-Blue.png
  55. BIN
      Plain Craft Launcher/Images/Checked-Gray.png
  56. BIN
      Plain Craft Launcher/Images/False.png
  57. BIN
      Plain Craft Launcher/Images/Hint-Critical.png
  58. BIN
      Plain Craft Launcher/Images/Hint-Finish.png
  59. BIN
      Plain Craft Launcher/Images/Hint-Info.png
  60. BIN
      Plain Craft Launcher/Images/Hint-Warn.png
  61. BIN
      Plain Craft Launcher/Images/Infobox-Bg.png
  62. BIN
      Plain Craft Launcher/Images/LeftBar-BG_Black.png
  63. BIN
      Plain Craft Launcher/Images/LeftBar-BG_Blue.png
  64. BIN
      Plain Craft Launcher/Images/LeftBar-BG_DeathBlue.png
  65. BIN
      Plain Craft Launcher/Images/LeftBar-BG_Green.png
  66. BIN
      Plain Craft Launcher/Images/LeftBar-BG_Hunluan.png
  67. BIN
      Plain Craft Launcher/Images/LeftBar-BG_Orange.png
  68. BIN
      Plain Craft Launcher/Images/Stats-Fail.png
  69. BIN
      Plain Craft Launcher/Images/Stats-Finish.png
  70. BIN
      Plain Craft Launcher/Images/Stats-Retry.png
  71. BIN
      Plain Craft Launcher/Images/Stats-Running.png
  72. BIN
      Plain Craft Launcher/Images/Stats-Wait.png
  73. BIN
      Plain Craft Launcher/Images/True.png
  74. BIN
      Plain Craft Launcher/Images/alex.png
  75. BIN
      Plain Craft Launcher/Images/appbar.inbox.in.png
  76. BIN
      Plain Craft Launcher/Images/appbar.settings.png
  77. BIN
      Plain Craft Launcher/Images/icon.ico
  78. BIN
      Plain Craft Launcher/Images/imgMusic.png
  79. BIN
      Plain Craft Launcher/Images/imgTopClose.png
  80. BIN
      Plain Craft Launcher/Images/imgTopMin.png
  81. BIN
      Plain Craft Launcher/Images/infobox-blue.png
  82. BIN
      Plain Craft Launcher/Images/infobox-brown.png
  83. BIN
      Plain Craft Launcher/Images/infobox-green.png
  84. BIN
      Plain Craft Launcher/Images/infobox-orange.png
  85. BIN
      Plain Craft Launcher/Images/infobox-purple.png
  86. BIN
      Plain Craft Launcher/Images/infobox-yellow.png
  87. BIN
      Plain Craft Launcher/Images/steve.png
  88. 58 0
      Plain Craft Launcher/My Project/AssemblyInfo.vb
  89. 121 0
      Plain Craft Launcher/My Project/MyExtensions/MyWpfExtension.vb
  90. 103 0
      Plain Craft Launcher/My Project/Resources.Designer.vb
  91. 133 0
      Plain Craft Launcher/My Project/Resources.resx
  92. 71 0
      Plain Craft Launcher/My Project/Settings.Designer.vb
  93. 7 0
      Plain Craft Launcher/My Project/Settings.settings
  94. 48 0
      Plain Craft Launcher/My Project/app.manifest
  95. 51 0
      Plain Craft Launcher/Pages/formDownloadLeft.xaml
  96. 1043 0
      Plain Craft Launcher/Pages/formDownloadLeft.xaml.vb
  97. 38 0
      Plain Craft Launcher/Pages/formDownloadRight.xaml
  98. 304 0
      Plain Craft Launcher/Pages/formDownloadRight.xaml.vb
  99. 81 0
      Plain Craft Launcher/Pages/formGuild.xaml
  100. 254 0
      Plain Craft Launcher/Pages/formGuild.xaml.vb

+ 79 - 0
Plain Craft Launcher Admin Manager/ApplicationEvents.vb

@@ -0,0 +1,79 @@
+Imports System.IO
+Imports System.Threading
+
+Namespace My
+    Partial Friend Class MyApplication
+        Public Shared Sub Main()
+            Try
+                '处理启动参数
+                Dim StartScript As String = ""
+                For Each Child As String In Application.CommandLineArgs
+                    StartScript = StartScript & Child & " "
+                Next
+                If StartScript.EndsWith(" ") Then StartScript = Mid(StartScript, 1, Len(StartScript) - 1)
+
+                '判断启动参数头
+                Dim Data As String = StartScript.Replace(Split(StartScript, ":")(0) & ":", "")
+                Select Case Split(StartScript, ":")(0)
+
+                    Case "Set Environment Variable"
+                        '设置环境变量
+                        Environment.SetEnvironmentVariable("Path", Data, EnvironmentVariableTarget.Machine)
+
+                    Case "Auto Update"
+                        '自动更新
+                        Dim RetryCount As Integer = 0
+                        Dim ProcessId As Integer = Val(Data.Split("|")(1))
+                        Data = Data.Split("|")(0)
+Retry:
+                        '关闭PCL进程
+                        Try
+                            Dim Process As Process = Process.GetProcessById(ProcessId)
+                            Process.Kill()
+                        Catch
+                        End Try
+                        Try
+                            Dim f = New Func
+                            f.CopyDerictory(New DirectoryInfo(Data & "PCL\Update\"), New DirectoryInfo(Data))
+                            Directory.Delete(Data & "PCL\Update", True)
+                            Shell(Data & "PCL.exe", AppWinStyle.NormalFocus)
+                        Catch ex As Exception
+                            RetryCount = RetryCount + 1
+                            If RetryCount < 10 Then
+                                Thread.Sleep(1500)
+                                GoTo Retry
+                            End If
+                            MsgBox("自动更新失败:" & ex.ToString, MsgBoxStyle.Critical, "Plain Craft Launcher - PCLAM")
+                        End Try
+
+                End Select
+
+            Catch ex As Exception
+                MsgBox("处理失败:" & ex.ToString, MsgBoxStyle.Critical, "Plain Craft Launcher - PCLAM")
+            End Try
+        End Sub
+
+    End Class
+End Namespace
+
+Public Class Func
+
+
+    Public Sub CopyDerictory(ByVal DirectorySrc As DirectoryInfo, ByVal DirectoryDes As DirectoryInfo)
+        Dim strDirectoryDesPath As String = DirectoryDes.FullName '& "" & DirectorySrc.Name
+        If Not Directory.Exists(strDirectoryDesPath) Then Directory.CreateDirectory(strDirectoryDesPath)
+        Dim f, fs() As FileInfo
+        fs = DirectorySrc.GetFiles()
+        For Each f In fs
+            File.Copy(f.FullName, strDirectoryDesPath & f.Name.ToString, True)
+        Next
+        Dim DirSrc, Dirs() As DirectoryInfo
+        Dirs = DirectorySrc.GetDirectories()
+        '递归调用自身
+        For Each DirSrc In Dirs
+            Dim DirDes As New DirectoryInfo(strDirectoryDesPath)
+            CopyDerictory(DirSrc, DirDes)
+        Next
+    End Sub
+
+End Class

+ 13 - 0
Plain Craft Launcher Admin Manager/My Project/Application.Designer.vb

@@ -0,0 +1,13 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+'     此代码由工具生成。
+'     运行时版本:4.0.30319.42000
+'
+'     对此文件的更改可能会导致不正确的行为,并且如果
+'     重新生成代码,这些更改将会丢失。
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict On
+Option Explicit On
+

+ 9 - 0
Plain Craft Launcher Admin Manager/My Project/Application.myapp

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<MyApplicationData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <MySubMain>false</MySubMain>
+  <SingleInstance>false</SingleInstance>
+  <ShutdownMode>0</ShutdownMode>
+  <EnableVisualStyles>true</EnableVisualStyles>
+  <AuthenticationMode>1</AuthenticationMode>
+  <SaveMySettingsOnExit>true</SaveMySettingsOnExit>
+</MyApplicationData>

+ 35 - 0
Plain Craft Launcher Admin Manager/My Project/AssemblyInfo.vb

@@ -0,0 +1,35 @@
+Imports System
+Imports System.Reflection
+Imports System.Runtime.InteropServices
+
+' 有关程序集的常规信息通过下列特性集
+' 控制。更改这些特性值可修改
+' 与程序集关联的信息。
+
+' 查看程序集特性的值
+
+<Assembly: AssemblyTitle("Plain Craft Launcher Admin Manager")>
+<Assembly: AssemblyDescription("Plain Craft Launcher 更新安装 & 环境变量设置")>
+<Assembly: AssemblyCompany("")> 
+<Assembly: AssemblyProduct("Plain Craft Launcher Admin Manager")> 
+<Assembly: AssemblyCopyright("Copyright © 龙腾猫跃 2016-2017")> 
+<Assembly: AssemblyTrademark("")> 
+
+<Assembly: ComVisible(False)>
+
+'如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+<Assembly: Guid("d7a19dd0-56d0-4a97-bc44-8cf920978ed4")>
+
+' 程序集的版本信息由下面四个值组成:
+'
+'      主版本
+'      次版本
+'      内部版本号
+'      修订号
+'
+' 可以指定所有这些值,也可以使用“内部版本号”和“修订号”的默认值,
+' 方法是按如下所示使用“*”:
+' <Assembly: AssemblyVersion("1.0.*")> 
+
+<Assembly: AssemblyVersion("1.3.0.0")>
+<Assembly: AssemblyFileVersion("1.3.0.0")>

+ 63 - 0
Plain Craft Launcher Admin Manager/My Project/Resources.Designer.vb

@@ -0,0 +1,63 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+'     此代码由工具生成。
+'     运行时版本:4.0.30319.42000
+'
+'     对此文件的更改可能会导致不正确的行为,并且如果
+'     重新生成代码,这些更改将会丢失。
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict On
+Option Explicit On
+
+Imports System
+
+Namespace My.Resources
+    
+    '此类是由 StronglyTypedResourceBuilder
+    '类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+    '若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+    '(以 /str 作为命令选项),或重新生成 VS 项目。
+    '''<summary>
+    '''  一个强类型的资源类,用于查找本地化的字符串等。
+    '''</summary>
+    <Global.System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0"),  _
+     Global.System.Diagnostics.DebuggerNonUserCodeAttribute(),  _
+     Global.System.Runtime.CompilerServices.CompilerGeneratedAttribute(),  _
+     Global.Microsoft.VisualBasic.HideModuleNameAttribute()>  _
+    Friend Module Resources
+        
+        Private resourceMan As Global.System.Resources.ResourceManager
+        
+        Private resourceCulture As Global.System.Globalization.CultureInfo
+        
+        '''<summary>
+        '''  返回此类使用的缓存的 ResourceManager 实例。
+        '''</summary>
+        <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)>  _
+        Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager
+            Get
+                If Object.ReferenceEquals(resourceMan, Nothing) Then
+                    Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("Plain_Craft_Launcher_Admin_Manager.Resources", GetType(Resources).Assembly)
+                    resourceMan = temp
+                End If
+                Return resourceMan
+            End Get
+        End Property
+        
+        '''<summary>
+        '''  使用此强类型资源类,为所有资源查找
+        '''  重写当前线程的 CurrentUICulture 属性。
+        '''</summary>
+        <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)>  _
+        Friend Property Culture() As Global.System.Globalization.CultureInfo
+            Get
+                Return resourceCulture
+            End Get
+            Set
+                resourceCulture = value
+            End Set
+        End Property
+    End Module
+End Namespace

+ 117 - 0
Plain Craft Launcher Admin Manager/My Project/Resources.resx

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 73 - 0
Plain Craft Launcher Admin Manager/My Project/Settings.Designer.vb

@@ -0,0 +1,73 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+'     此代码由工具生成。
+'     运行时版本:4.0.30319.42000
+'
+'     对此文件的更改可能会导致不正确的行为,并且如果
+'     重新生成代码,这些更改将会丢失。
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict On
+Option Explicit On
+
+
+Namespace My
+    
+    <Global.System.Runtime.CompilerServices.CompilerGeneratedAttribute(),  _
+     Global.System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0"),  _
+     Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)>  _
+    Partial Friend NotInheritable Class MySettings
+        Inherits Global.System.Configuration.ApplicationSettingsBase
+        
+        Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings()),MySettings)
+        
+#Region "My.Settings 自动保存功能"
+#If _MyType = "WindowsForms" Then
+    Private Shared addedHandler As Boolean
+
+    Private Shared addedHandlerLockObject As New Object
+
+    <Global.System.Diagnostics.DebuggerNonUserCodeAttribute(), Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
+    Private Shared Sub AutoSaveSettings(ByVal sender As Global.System.Object, ByVal e As Global.System.EventArgs)
+        If My.Application.SaveMySettingsOnExit Then
+            My.Settings.Save()
+        End If
+    End Sub
+#End If
+#End Region
+        
+        Public Shared ReadOnly Property [Default]() As MySettings
+            Get
+                
+#If _MyType = "WindowsForms" Then
+               If Not addedHandler Then
+                    SyncLock addedHandlerLockObject
+                        If Not addedHandler Then
+                            AddHandler My.Application.Shutdown, AddressOf AutoSaveSettings
+                            addedHandler = True
+                        End If
+                    End SyncLock
+                End If
+#End If
+                Return defaultInstance
+            End Get
+        End Property
+    End Class
+End Namespace
+
+Namespace My
+    
+    <Global.Microsoft.VisualBasic.HideModuleNameAttribute(),  _
+     Global.System.Diagnostics.DebuggerNonUserCodeAttribute(),  _
+     Global.System.Runtime.CompilerServices.CompilerGeneratedAttribute()>  _
+    Friend Module MySettingsProperty
+        
+        <Global.System.ComponentModel.Design.HelpKeywordAttribute("My.Settings")>  _
+        Friend ReadOnly Property Settings() As Global.Plain_Craft_Launcher_Admin_Manager.My.MySettings
+            Get
+                Return Global.Plain_Craft_Launcher_Admin_Manager.My.MySettings.Default
+            End Get
+        End Property
+    End Module
+End Namespace

+ 7 - 0
Plain Craft Launcher Admin Manager/My Project/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" UseMySettingsClassName="true">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 48 - 0
Plain Craft Launcher Admin Manager/My Project/app.manifest

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <!-- UAC 清单选项
+            如果要更改 Windows 用户帐户控制级别,请用以下节点之一替换 
+            requestedExecutionLevel 节点。
+
+        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
+        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
+        <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />
+
+            指定 requestedExecutionLevel 节点将会禁用文件和注册表虚拟化。
+            如果要利用文件和注册表虚拟化实现向后
+            兼容性,则删除 requestedExecutionLevel 节点。
+        -->
+        <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+  
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- 此应用程序设计使用的所有 Windows 版本的列表。Windows 将会自动选择最兼容的环境。-->
+
+      <!-- 如果应用程序设计使用 Windows 7,请取消注释以下 supportedOS 节点-->
+      <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>-->
+      
+    </application>
+  </compatibility>
+  
+  <!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
+  <!-- <dependency>
+    <dependentAssembly>
+      <assemblyIdentity
+          type="win32"
+          name="Microsoft.Windows.Common-Controls"
+          version="6.0.0.0"
+          processorArchitecture="*"
+          publicKeyToken="6595b64144ccf1df"
+          language="*"
+        />
+    </dependentAssembly>
+  </dependency>-->
+
+</asmv1:assembly>

+ 128 - 0
Plain Craft Launcher Admin Manager/Plain Craft Launcher Admin Manager.vbproj

@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+    <ProductVersion>
+    </ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{A4D5D793-DEE9-431C-92BD-B1B2B5A0356F}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <StartupObject>Sub Main</StartupObject>
+    <RootNamespace>Plain_Craft_Launcher_Admin_Manager</RootNamespace>
+    <AssemblyName>Plain Craft Launcher Admin Manager</AssemblyName>
+    <FileAlignment>512</FileAlignment>
+    <MyType>WindowsFormsWithCustomSubMain</MyType>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <TargetFrameworkProfile>Client</TargetFrameworkProfile>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <DefineDebug>true</DefineDebug>
+    <DefineTrace>true</DefineTrace>
+    <OutputPath>..\Debug</OutputPath>
+    <DocumentationFile>Plain Craft Launcher Admin Manager.xml</DocumentationFile>
+    <NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <PlatformTarget>x86</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <DefineDebug>false</DefineDebug>
+    <DefineTrace>true</DefineTrace>
+    <Optimize>true</Optimize>
+    <OutputPath>..\Debug</OutputPath>
+    <DocumentationFile>Plain Craft Launcher Admin Manager.xml</DocumentationFile>
+    <NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
+  </PropertyGroup>
+  <PropertyGroup>
+    <OptionExplicit>On</OptionExplicit>
+  </PropertyGroup>
+  <PropertyGroup>
+    <OptionCompare>Binary</OptionCompare>
+  </PropertyGroup>
+  <PropertyGroup>
+    <OptionStrict>Off</OptionStrict>
+  </PropertyGroup>
+  <PropertyGroup>
+    <OptionInfer>On</OptionInfer>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationManifest>My Project\app.manifest</ApplicationManifest>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationIcon>
+    </ApplicationIcon>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.ServiceProcess" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+  </ItemGroup>
+  <ItemGroup>
+    <Import Include="Microsoft.VisualBasic" />
+    <Import Include="System" />
+    <Import Include="System.Collections" />
+    <Import Include="System.Collections.Generic" />
+    <Import Include="System.Data" />
+    <Import Include="System.Drawing" />
+    <Import Include="System.Diagnostics" />
+    <Import Include="System.Windows.Forms" />
+    <Import Include="System.Linq" />
+    <Import Include="System.Xml.Linq" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="ApplicationEvents.vb" />
+    <Compile Include="My Project\AssemblyInfo.vb" />
+    <Compile Include="My Project\Application.Designer.vb">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Application.myapp</DependentUpon>
+    </Compile>
+    <Compile Include="My Project\Resources.Designer.vb">
+      <AutoGen>True</AutoGen>
+      <DesignTime>True</DesignTime>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <Compile Include="My Project\Settings.Designer.vb">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="My Project\Resources.resx">
+      <Generator>VbMyResourcesResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.vb</LastGenOutput>
+      <CustomToolNamespace>My.Resources</CustomToolNamespace>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="My Project\app.manifest" />
+    <None Include="My Project\Application.myapp">
+      <Generator>MyApplicationCodeGenerator</Generator>
+      <LastGenOutput>Application.Designer.vb</LastGenOutput>
+    </None>
+    <None Include="My Project\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <CustomToolNamespace>My</CustomToolNamespace>
+      <LastGenOutput>Settings.Designer.vb</LastGenOutput>
+    </None>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

+ 31 - 0
Plain Craft Launcher.sln

@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31410.357
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Plain Craft Launcher", "Plain Craft Launcher\Plain Craft Launcher.vbproj", "{D73DA5E6-9D38-4559-8904-1CFF2434415A}"
+EndProject
+Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Plain Craft Launcher Admin Manager", "Plain Craft Launcher Admin Manager\Plain Craft Launcher Admin Manager.vbproj", "{A4D5D793-DEE9-431C-92BD-B1B2B5A0356F}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x86 = Debug|x86
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{D73DA5E6-9D38-4559-8904-1CFF2434415A}.Debug|x86.ActiveCfg = Debug|x86
+		{D73DA5E6-9D38-4559-8904-1CFF2434415A}.Debug|x86.Build.0 = Debug|x86
+		{D73DA5E6-9D38-4559-8904-1CFF2434415A}.Release|x86.ActiveCfg = Release|x86
+		{D73DA5E6-9D38-4559-8904-1CFF2434415A}.Release|x86.Build.0 = Release|x86
+		{A4D5D793-DEE9-431C-92BD-B1B2B5A0356F}.Debug|x86.ActiveCfg = Debug|x86
+		{A4D5D793-DEE9-431C-92BD-B1B2B5A0356F}.Debug|x86.Build.0 = Debug|x86
+		{A4D5D793-DEE9-431C-92BD-B1B2B5A0356F}.Release|x86.ActiveCfg = Release|x86
+		{A4D5D793-DEE9-431C-92BD-B1B2B5A0356F}.Release|x86.Build.0 = Release|x86
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {1A8E94D0-205A-4FF4-A6F9-390DF3DD7A0A}
+	EndGlobalSection
+EndGlobal

+ 48 - 0
Plain Craft Launcher.vshost.exe.manifest

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <!-- UAC 清单选项
+            如果要更改 Windows 用户帐户控制级别,请用以下节点之一替换 
+            requestedExecutionLevel 节点。
+
+        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
+        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
+        <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />
+
+            指定 requestedExecutionLevel 节点将会禁用文件和注册表虚拟化。
+            如果要利用文件和注册表虚拟化实现向后
+            兼容性,则删除 requestedExecutionLevel 节点。
+        -->
+        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+  
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- 此应用程序设计使用的所有 Windows 版本的列表。Windows 将会自动选择最兼容的环境。-->
+
+      <!-- 如果应用程序设计使用 Windows 7,请取消注释以下 supportedOS 节点-->
+      <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>-->
+      
+    </application>
+  </compatibility>
+  
+  <!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
+  <dependency>
+    <dependentAssembly>
+      <assemblyIdentity
+          type="win32"
+          name="Microsoft.Windows.Common-Controls"
+          version="6.0.0.0"
+          processorArchitecture="*"
+          publicKeyToken="6595b64144ccf1df"
+          language="*"
+        />
+    </dependentAssembly>
+  </dependency>
+
+</asmv1:assembly>

+ 222 - 0
Plain Craft Launcher.xml

@@ -0,0 +1,222 @@
+<?xml version="1.0"?>
+<doc>
+<assembly>
+<name>
+pcl
+</name>
+</assembly>
+<members>
+<member name="M:pcl.formLaunchDownload.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.formLaunchDownload">
+	<summary>
+formLaunchDownload
+</summary>
+</member><member name="M:pcl.XamlGeneratedNamespace.GeneratedInternalTypeHelper.CreateInstance(System.Type,System.Globalization.CultureInfo)">
+	<summary>
+CreateInstance
+</summary>
+</member><member name="M:pcl.XamlGeneratedNamespace.GeneratedInternalTypeHelper.GetPropertyValue(System.Reflection.PropertyInfo,System.Object,System.Globalization.CultureInfo)">
+	<summary>
+GetPropertyValue
+</summary>
+</member><member name="M:pcl.XamlGeneratedNamespace.GeneratedInternalTypeHelper.SetPropertyValue(System.Reflection.PropertyInfo,System.Object,System.Object,System.Globalization.CultureInfo)">
+	<summary>
+SetPropertyValue
+</summary>
+</member><member name="M:pcl.XamlGeneratedNamespace.GeneratedInternalTypeHelper.CreateDelegate(System.Type,System.Object,System.String)">
+	<summary>
+CreateDelegate
+</summary>
+</member><member name="M:pcl.XamlGeneratedNamespace.GeneratedInternalTypeHelper.AddEventHandler(System.Reflection.EventInfo,System.Object,System.Delegate)">
+	<summary>
+AddEventHandler
+</summary>
+</member><member name="T:pcl.XamlGeneratedNamespace.GeneratedInternalTypeHelper">
+	<summary>
+GeneratedInternalTypeHelper
+</summary>
+</member><member name="M:pcl.Button.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.Button">
+	<summary>
+Button
+</summary>
+</member><member name="M:pcl.Radiobox.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.Radiobox">
+	<summary>
+Radiobox
+</summary>
+</member><member name="M:pcl.Application.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="M:pcl.Application.Main">
+	<summary>
+Application Entry Point.
+</summary>
+</member><member name="T:pcl.Application">
+	<summary>
+Application
+</summary>
+</member><member name="M:pcl.JumpupHint.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.JumpupHint">
+	<summary>
+JumpupHint
+</summary>
+</member><member name="M:pcl.formStart.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.formStart">
+	<summary>
+formStart
+</summary>
+</member><member name="M:pcl.MCButton.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.MCButton">
+	<summary>
+MCButton
+</summary>
+</member><member name="M:pcl.Checkbox.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.Checkbox">
+	<summary>
+Checkbox
+</summary>
+</member><member name="M:pcl.formLaunchManage.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.formLaunchManage">
+	<summary>
+formLaunchManage
+</summary>
+</member><member name="M:pcl.MetroMsgbox.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.MetroMsgbox">
+	<summary>
+MetroMsgbox
+</summary>
+</member><member name="M:pcl.RoundProcessbar.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.RoundProcessbar">
+	<summary>
+RoundProcessbar
+</summary>
+</member><member name="M:pcl.formLaunch.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.formLaunch">
+	<summary>
+formLaunch
+</summary>
+</member><member name="M:pcl.formMain.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.formMain">
+	<summary>
+formMain
+</summary>
+</member><member name="M:pcl.VScroll.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.VScroll">
+	<summary>
+VScroll
+</summary>
+</member><member name="M:pcl.formLaunchNews.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.formLaunchNews">
+	<summary>
+formLaunchNews
+</summary>
+</member><member name="M:pcl.ListItem.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.ListItem">
+	<summary>
+ListItem
+</summary>
+</member><member name="M:pcl.Processbar.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.Processbar">
+	<summary>
+Processbar
+</summary>
+</member><member name="M:pcl.Timer.InitializeComponent">
+	<summary>
+InitializeComponent
+</summary>
+</member><member name="T:pcl.Timer">
+	<summary>
+Timer
+</summary>
+</member><member name="P:pcl.My.Resources.Resources.ResourceManager">
+	<summary>
+  返回此类使用的缓存的 ResourceManager 实例。
+</summary>
+</member><member name="P:pcl.My.Resources.Resources.Culture">
+	<summary>
+  使用此强类型资源类,为所有资源查找
+  重写当前线程的 CurrentUICulture 属性。
+</summary>
+</member><member name="T:pcl.My.Resources.Resources">
+	<summary>
+  一个强类型的资源类,用于查找本地化的字符串等。
+</summary>
+</member><member name="P:pcl.My.MyWpfExtension.Application">
+	<summary>
+ 返回正在运行的应用程序的应用程序对象
+ </summary>
+</member><member name="P:pcl.My.MyWpfExtension.Computer">
+	<summary>
+ 返回有关主机计算机的信息。
+ </summary>
+</member><member name="P:pcl.My.MyWpfExtension.User">
+	<summary>
+ 返回当前用户的信息。如果希望使用当前的 
+ Windows 用户凭据来运行应用程序,请调用 My.User.InitializeWithWindowsUser()。
+ </summary>
+</member><member name="P:pcl.My.MyWpfExtension.Log">
+	<summary>
+ 返回应用程序日志。可以使用应用程序的配置文件配置侦听器。
+ </summary>
+</member><member name="P:pcl.My.MyWpfExtension.Windows">
+	<summary>
+ 返回项目中定义的 Windows 集合。
+ </summary>
+</member><member name="T:pcl.My.MyWpfExtension">
+	<summary>
+ 用于定义“我的 WPF 命名空间”中的可用属性的模块
+ </summary>
+	<remarks></remarks>
+</member>
+</members>
+</doc>

+ 70 - 0
Plain Craft Launcher/Application.xaml

@@ -0,0 +1,70 @@
+<Application x:Class="Application"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:sys="clr-namespace:System;assembly=mscorlib" 
+    xmlns:my="clr-namespace:pcl"
+    xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
+    StartupUri="formStart.xaml"
+ShutdownMode="OnMainWindowClose">
+    <Application.Resources>
+
+        <!--Label-->
+        <Style TargetType="Label">
+            <Setter Property="OverridesDefaultStyle" Value="True"/>
+            <Setter Property="Padding" Value="5"/>
+            <!--防止Alt-->
+            <Setter Property="Template">
+                <Setter.Value>
+                    <ControlTemplate TargetType="Label">
+                        <Border Background="{TemplateBinding Background}">
+                            <ContentPresenter 
+                                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
+                                Margin="{TemplateBinding Padding}" 
+                                RecognizesAccessKey="False" 
+                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
+                                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
+                        </Border>
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
+        </Style>
+
+        <!--TextBox-->
+        <Style TargetType="TextBox">
+            <!--防止指向动画-->
+            <Setter Property="Template">
+                <Setter.Value>
+                    <ControlTemplate TargetType="TextBox">
+                        <Themes:ListBoxChrome x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
+                            <Grid>
+                                <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
+                            </Grid>
+                        </Themes:ListBoxChrome>
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
+            <!--样式-->
+            <Setter Property="Background" Value="Transparent"/>
+            <Setter Property="Height" Value="27" />
+            <Setter Property="VerticalContentAlignment" Value="Center" />
+            <Setter Property="SelectionOpacity" Value="0.2" />
+            <Setter Property="SelectionBrush" Value="{DynamicResource Color3}" />
+            <Setter Property="FontSize" Value="14" />
+            <Setter Property="Padding" Value="3,0,0,0" />
+            <Setter Property="BorderBrush" Value="{DynamicResource Color4}" />
+        </Style>
+        
+        <!--主题-->
+        <SolidColorBrush x:Key="Color1">#FFD5E9FF</SolidColorBrush>
+        <SolidColorBrush x:Key="Color2">#FF6AB1FF</SolidColorBrush>
+        <SolidColorBrush x:Key="Color3">#FF0079FF</SolidColorBrush>
+        <SolidColorBrush x:Key="Color4">#FF0051AA</SolidColorBrush>
+        <SolidColorBrush x:Key="Color5">#FF002855</SolidColorBrush>
+        <Color x:Key="ColorE1">#FFD5E9FF</Color>
+        <Color x:Key="ColorE2">#FF6AB1FF</Color>
+        <Color x:Key="ColorE3">#FF0079FF</Color>
+        <Color x:Key="ColorE4">#FF0051AA</Color>
+        <Color x:Key="ColorE5">#FF002855</Color>
+
+    </Application.Resources>
+</Application>

+ 52 - 0
Plain Craft Launcher/Application.xaml.vb

@@ -0,0 +1,52 @@
+Imports System.Reflection
+
+Class Application
+
+    Private Sub Application_Startup(ByVal sender As Object, ByVal e As System.Windows.StartupEventArgs) Handles Me.Startup
+        Try
+            Process.GetCurrentProcess.PriorityClass = ProcessPriorityClass.High
+            Application.Current.Dispatcher.Thread.Priority = ThreadPriority.AboveNormal
+            '基础初始化
+            AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf AssemblyResolve '动态 DLL 调用
+            LoadTimeCost = My.Computer.Clock.TickCount
+            Directory.CreateDirectory(PATH & "PCL")
+            Directory.CreateDirectory(PATH & "PCL\cache")
+            Directory.CreateDirectory(PATH & "PCL\download")
+            Try
+                File.Delete(PATH & "PCL\log.txt")
+            Catch
+            End Try
+            log("[Application] 程序启动,版本:" & VERSION_NAME & "(" & VERSION_CODE & ")")
+            '读取设置
+            MODE_DEBUG = ReadReg("SysTest", "False")
+            MODE_DEVELOPER = File.Exists(PATH & "Plain Craft Launcher Developer Tag")
+            '配置初始化
+            Net.ServicePointManager.DefaultConnectionLimit = 1024
+        Catch ex As Exception
+            If MsgBox("程序初始化时出现异常:" & GetStringFromException(ex, True) & vbCrLf & "是否愿意打开反馈页面来反馈这个问题?" & vbCrLf & "这会帮助作者解决问题,否则它将永远不会得到解决!", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, "炸了") = MsgBoxResult.Yes Then Feedback()
+        End Try
+    End Sub
+    Private Sub Application_DispatcherUnhandledException(ByVal sender As Object, ByVal e As System.Windows.Threading.DispatcherUnhandledExceptionEventArgs) Handles Me.DispatcherUnhandledException
+        On Error Resume Next
+        log("[Application] 请把这个 Log 文件发送给作者,以助于改进这个启动器,谢谢~")
+        log("[Application] 异常的详细信息:" & vbCrLf & GetStringFromException(e.Exception, True))
+        If MsgBox("程序出现异常:" & GetStringFromException(e.Exception, True) & vbCrLf & "是否愿意打开反馈页面来反馈这个问题?" & vbCrLf & "这会帮助作者解决问题,否则它将永远不会得到解决!", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, "炸了") = MsgBoxResult.Yes Then Feedback()
+        EndForce()
+    End Sub
+
+    '动态 DLL 调用
+    Private Ionic As Assembly = Assembly.Load(My.Resources.ResourceManager.GetObject("Ionic_Zip"))
+    Private Newtonsoft As Assembly = Assembly.Load(My.Resources.ResourceManager.GetObject("Newtonsoft_Json"))
+    Private Function AssemblyResolve(sender As Object, args As ResolveEventArgs) As Assembly
+        Dim Name As String = New AssemblyName(args.Name).Name
+        Select Case Name
+            Case "Ionic.Zip"
+                Return Ionic
+            Case "Newtonsoft.Json"
+                Return Newtonsoft
+            Case Else
+                Return Nothing
+        End Select
+    End Function
+
+End Class

+ 10 - 0
Plain Craft Launcher/Base/Button.xaml

@@ -0,0 +1,10 @@
+<UserControl x:Class="Button"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             mc:Ignorable="d" BorderBrush="#FF303030" Focusable="True" FontSize="14" Name="panMain" Background="#01F8F8F8" Opacity="0.8" UseLayoutRounding="True" BorderThickness="0" Padding="0,0,1,0">
+    <Border CornerRadius="1" Name="Border" BorderThickness="1" BorderBrush="{Binding ElementName=panMain, Path=BorderBrush}" Background="#7FFFFFFF" UseLayoutRounding="True">
+        <Label Content="Command" FontFamily="{Binding RelativeSource={RelativeSource Self}, Path=Parent.Parent.FontFamily}" FontSize="{Binding RelativeSource={RelativeSource Self}, Path=Parent.Parent.FontSize}" FontWeight="{Binding RelativeSource={RelativeSource Self}, Path=Parent.Parent.FontWeight}" HorizontalAlignment="Center" Name="labText" VerticalAlignment="Center" Foreground="{Binding ElementName=panMain, Path=BorderBrush}" Padding="0,0,0,1" Margin="0" />
+    </Border>
+</UserControl>

+ 111 - 0
Plain Craft Launcher/Base/Button.xaml.vb

@@ -0,0 +1,111 @@
+Public Class Button
+
+    '声明
+    Public Event Click(ByVal sender As Object, ByVal e As EventArgs) '自定义事件
+
+    '自定义属性 
+    Public Property Text As String
+        Get
+            Return labText.Content
+        End Get
+        Set(ByVal value As String)
+            labText.Content = value
+        End Set
+    End Property '显示文本
+    Public Property TextPadding As Thickness
+        Get
+            Return labText.Padding
+        End Get
+        Set(ByVal value As Thickness)
+            labText.Padding = value
+        End Set
+    End Property
+    Private _ColorType As State = State.COMMON  '配色方案
+    Public Property ColorType As State
+        Get
+            Return _ColorType
+        End Get
+        Set(ByVal value As State)
+            _ColorType = value
+            RefreshColor()
+        End Set
+    End Property
+    Public Enum State As Byte
+        COMMON = 0
+        HIGHLIGHT = 1
+        RED = 2
+    End Enum
+
+    '自定义事件
+    Private Sub RefreshColor() Handles Me.MouseEnter, Me.MouseLeave, Me.Loaded, Me.IsEnabledChanged
+        Try
+            If Me.IsEnabled Then
+                Select Case Me._ColorType '选取颜色种类
+                    Case State.COMMON
+                        If Me.IsMouseOver Then
+                            '指向
+                            AniStart({ _
+                                     AaBackGround(Border, New MyColor(Color.FromArgb(255, 248, 248, 248)) - Border.Background, 150),
+                                     AaBorderBrush(Me, New MyColor(Color.FromArgb(255, 42, 42, 42)) - Me.BorderBrush, 150),
+                                     AaOpacity(Me, 1 - Me.Opacity, 200)
+                                 }, "BtnColor" & Me.Name)
+                        Else
+                            '普通
+                            AniStart({ _
+                                     AaBackGround(Border, New MyColor(Color.FromArgb(128, 248, 248, 248)) - Border.Background, 150),
+                                     AaBorderBrush(Me, New MyColor(Color.FromArgb(255, 42, 42, 42)) - Me.BorderBrush, 150),
+                                     AaOpacity(Me, 0.8 - Me.Opacity, 200)
+                                 }, "BtnColor" & Me.Name)
+                        End If
+                    Case State.HIGHLIGHT
+                        If Me.IsMouseOver Then
+                            '高亮指向
+                            AniStart({
+                                     AaBackGround(Border, New MyColor(Color.FromArgb(255, 248, 248, 248)) - Border.Background, 150),
+                                     AaBorderBrush(Me, New MyColor(CType(Application.Current.Resources("Color5"), SolidColorBrush).Color) - Me.BorderBrush, 150),
+                                     AaOpacity(Me, 1 - Me.Opacity, 200)
+                                 }, "BtnColor" & Me.Name)
+                        Else
+                            '高亮普通
+                            AniStart({
+                                     AaBackGround(Border, New MyColor(Color.FromArgb(128, 248, 248, 248)) - Border.Background, 150),
+                                     AaBorderBrush(Me, New MyColor(CType(Application.Current.Resources("Color5"), SolidColorBrush).Color) - Me.BorderBrush, 150),
+                                     AaOpacity(Me, 0.8 - Me.Opacity, 200)
+                                 }, "BtnColor" & Me.Name)
+                        End If
+                    Case State.RED
+                        If Me.IsMouseOver Then
+                            '红色指向
+                            AniStart({ _
+                                     AaBackGround(Border, New MyColor(Color.FromArgb(255, 248, 248, 248)) - Border.Background, 150),
+                                     AaBorderBrush(Me, New MyColor(Color.FromArgb(255, 200, 20, 20)) - Me.BorderBrush, 150),
+                                     AaOpacity(Me, 1 - Me.Opacity, 200)
+                                 }, "BtnColor" & Me.Name)
+                        Else
+                            '红色普通
+                            AniStart({ _
+                                     AaBackGround(Border, New MyColor(Color.FromArgb(128, 248, 248, 248)) - Border.Background, 150),
+                                     AaBorderBrush(Me, New MyColor(Color.FromArgb(255, 200, 20, 20)) - Me.BorderBrush, 150),
+                                     AaOpacity(Me, 0.8 - Me.Opacity, 200)
+                                 }, "BtnColor" & Me.Name)
+                        End If
+                End Select
+            Else
+                '灰色(不可用)
+                AniStart({ _
+                        AaBackGround(Border, New MyColor(Color.FromArgb(128, 248, 248, 248)) - Border.Background, 150),
+                        AaBorderBrush(Me, New MyColor(Color.FromArgb(255, 144, 144, 144)) - Me.BorderBrush, 150),
+                        AaOpacity(Me, 0.8 - Me.Opacity, 200)
+                     }, "BtnColor" & Me.Name)
+            End If
+        Catch ex As Exception
+            ExShow(ex, "刷新按钮出错")
+        End Try
+    End Sub
+
+    '默认事件
+    Private Sub Button_Mouse(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
+        RaiseEvent Click(sender, e)
+    End Sub
+
+End Class

+ 23 - 0
Plain Craft Launcher/Base/Checkbox.xaml

@@ -0,0 +1,23 @@
+<UserControl x:Class="Checkbox"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+              xmlns:my="clr-namespace:pcl"
+             mc:Ignorable="d" 
+             d:DesignHeight="33" d:DesignWidth="103" MinWidth="20" MinHeight="20" Background="#00000000" Foreground="#FF323232" FontSize="13">
+    <Grid HorizontalAlignment="Left" MinHeight="18" MaxHeight="18" MinWidth="20" Name="panMain" Opacity="0.8">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="18" />
+            <ColumnDefinition Width="Auto" />
+        </Grid.ColumnDefinitions>
+        <Label Name="label" Grid.Column="1" Padding="0" Margin="8,0,0,0" VerticalContentAlignment="Center" Height="18" VerticalAlignment="Top" BorderBrush="Black" Content="Checkbox" Foreground="{Binding RelativeSource={RelativeSource Self}, Path=Parent.Parent.Foreground}" FontFamily="{Binding RelativeSource={RelativeSource Self}, Path=Parent.Parent.FontFamily}" FontSize="13" />
+        <Border BorderBrush="#FF333333" BorderThickness="2" Height="18" HorizontalAlignment="Left" Name="border" VerticalAlignment="Top" Width="18" CornerRadius="1">
+            <Grid Height="14" Name="Check" Width="14">
+                <Image Height="14" Name="imgBlack" Source="/pcl;component/Images/Checked-Black.png" Width="14" />
+                <Image Height="14" Name="imgBlue" Source="/pcl;component/Images/Checked-Blue.png" Width="14" Opacity="0" />
+                <Image Height="14"  Name="imgGray" Source="/pcl;component/Images/Checked-Gray.png" Width="14" Opacity="0" />
+            </Grid>
+        </Border>
+    </Grid>
+</UserControl>

+ 134 - 0
Plain Craft Launcher/Base/Checkbox.xaml.vb

@@ -0,0 +1,134 @@
+Public Class Checkbox
+
+    Public Event Change(ByVal sender As Object, ByVal raiseByMouse As Boolean)
+
+    '自定义属性
+    Private _Checked As Boolean = True   '是否选中
+    Public Property Checked As Boolean
+        Get
+            Return _Checked
+        End Get
+        Set(ByVal value As Boolean)
+            If Not value = _Checked Then '只有改变了才触发
+                _Checked = value
+                If Me.IsLoaded Then RaiseEvent Change(Me, False) 'Change事件只在控件加载后触发,以防止默认属性设置触发事件
+                If Me.IsLoaded And UseControlAnimation Then '防止默认属性设置触发动画
+                    Refresh()
+                Else
+                    Check.Opacity = If(_Checked, 1, 0)
+                End If
+            End If
+        End Set
+    End Property
+    Public Property Context As String
+        Get
+            Return label.Content
+        End Get
+        Set(ByVal value As String)
+            label.Content = value
+        End Set
+    End Property '内容
+
+    '自定义事件
+    Private Sub Refresh()
+        'Checked变更
+        If _Checked Then
+            AniStop("Ch" & Me.Name & "CheckedChange") '停止旧动画实行相对动画
+            AniStart({
+                     AaScale(border, 4 / border.Width - 1, 150),
+                     AaOpacity(Check, 1 - Check.Opacity, 1, , , True),
+                     AaX(imgBlack, -14 - imgBlack.Margin.Left, 1),
+                     AaY(imgBlack, -14 - imgBlack.Margin.Left, 1),
+                     AaX(imgBlue, -14 - imgBlue.Margin.Left, 1),
+                     AaY(imgBlue, -14 - imgBlue.Margin.Left, 1),
+                     AaX(imgGray, -14 - imgGray.Margin.Left, 1),
+                     AaY(imgGray, -14 - imgGray.Margin.Left, 1),
+                     AaX(imgBlack, 14, 150, , , True),
+                     AaY(imgBlack, 14, 150),
+                     AaX(imgBlue, 14, 150),
+                     AaY(imgBlue, 14, 150),
+                     AaX(imgGray, 14, 150),
+                     AaY(imgGray, 14, 150),
+                     AaScale(border, 14 / border.Width, 150)
+            }, "Ch" & Me.Name & "CheckedChange")
+        Else
+            AniStop("Ch" & Me.Name & "CheckedChange")
+            AniStart({ _
+                     AaX(imgBlack, imgBlack.Margin.Left - 14, 150),
+                     AaY(imgBlack, imgBlack.Margin.Left - 14, 150),
+                     AaX(imgBlue, imgBlue.Margin.Left - 14, 150),
+                     AaY(imgBlue, imgBlue.Margin.Left - 14, 150),
+                     AaX(imgGray, imgGray.Margin.Left - 14, 150),
+                     AaY(imgGray, imgGray.Margin.Left - 14, 150),
+                     AaScale(border, 4 / border.Width - 1, 150),
+                     AaOpacity(Check, -Check.Opacity, 1, , , True),
+                     AaScale(border, 14 / border.Width, 150) _
+            }, "Ch" & Me.Name & "CheckedChange")
+        End If
+    End Sub
+
+    '默认事件
+    Private Sub Checkbox_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles Me.MouseDown
+        _Checked = Not _Checked
+        If Me.IsLoaded Then
+            RaiseEvent Change(Me, True) 'Change事件只在控件加载后触发,以防止默认属性设置触发事件
+            Refresh()
+        Else
+            Check.Opacity = If(_Checked, 1, 0)
+        End If
+    End Sub
+    Private Sub Checkbox_IsEnabledChanged(ByVal sender As Object, ByVal e As System.Windows.DependencyPropertyChangedEventArgs) Handles Me.IsEnabledChanged
+        If Me.IsLoaded And UseControlAnimation Then  '防止默认属性设置触发动画
+            If Me.IsEnabled Then
+                AniStop("Ch" & Me.Name & "PointChange")
+                AniStart({
+                         AaOpacity(imgBlack, 1 - imgBlack.Opacity, 150),
+                         AaBorderBrush(border, New MyColor(50, 50, 50) - border.BorderBrush, 150),
+                         AaForeGround(Me, New MyColor(50, 50, 50) - Me.Foreground, 150),
+                         AaOpacity(imgBlue, -imgBlue.Opacity, 150, 50),
+                         AaOpacity(imgGray, -imgGray.Opacity, 150, 50)
+                }, "Ch" & Me.Name & "PointChange")
+            Else
+                AniStop("Ch" & Me.Name & "PointChange")
+                AniStart({
+                         AaOpacity(imgGray, 1 - imgGray.Opacity, 150),
+                         AaBorderBrush(border, New MyColor(145, 145, 145) - border.BorderBrush, 150),
+                         AaForeGround(Me, New MyColor(145, 145, 145) - Me.Foreground, 150),
+                         AaOpacity(imgBlue, -imgBlue.Opacity, 150, 50),
+                         AaOpacity(imgBlack, -imgBlack.Opacity, 150, 50)
+                }, "Ch" & Me.Name & "PointChange")
+            End If
+        Else
+            If Me.IsEnabled Then
+                imgGray.Opacity = 0
+                imgBlack.Opacity = 1
+                border.BorderBrush = New MyColor(50, 50, 50)
+                Me.Foreground = New MyColor(50, 50, 50)
+            Else
+                imgGray.Opacity = 1
+                imgBlack.Opacity = 0
+                border.BorderBrush = New MyColor(145, 145, 145)
+                Me.Foreground = New MyColor(145, 145, 145)
+            End If
+        End If
+    End Sub
+
+    '鼠标指向
+    Private Sub Checkbox_MouseEnter(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles Me.MouseEnter
+        If Me.Name = "" Then Me.Name = "Check" & GetUUID()
+        AniStop("Ch" & Me.Name & "PointChange")
+        AniStart({
+                     AaOpacity(panMain, 1 - panMain.Opacity, 200)
+        }, "Ch" & Me.Name & "PointChange")
+    End Sub
+    Private Sub Checkbox_MouseLeave(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles Me.MouseLeave
+        'MouseLeave比IsEnabledChanged后执行,所以如果自定义事件修改了IsEnabled,将导致颜色显示错误,这个判断语句为了修复此bug
+        If Me.IsEnabled Then
+            AniStop("Ch" & Me.Name & "PointChange")
+            AniStart({
+                     AaOpacity(panMain, 0.8 - panMain.Opacity, 200)
+            }, "Ch" & Me.Name & "PointChange")
+        End If
+    End Sub
+
+End Class

+ 62 - 0
Plain Craft Launcher/Base/InfoBox.xaml

@@ -0,0 +1,62 @@
+<UserControl x:Class="InfoBox"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             mc:Ignorable="d" 
+             d:DesignHeight="300" d:DesignWidth="300" Width="230" Height="120" UseLayoutRounding="True">
+    <Border x:Name="panBackAll">
+        <Border.Background>
+            <ImageBrush ImageSource="/pcl;component/Images/Infobox-Bg.png" />
+        </Border.Background>
+        <Grid Width="220" Height="110" Name="panBack" Background="{x:Null}">
+            <Border BorderThickness="1" Width="222" Height="112" Margin="-1,-1,0,0">
+                <Image Name="imgBack" Stretch="UniformToFill" HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="220" MinHeight="110">
+                    <Image.Effect>
+                        <BlurEffect x:Name="blur" Radius="4"/>
+                    </Image.Effect>
+                </Image>
+                <Border.BorderBrush>
+                    <SolidColorBrush />
+                </Border.BorderBrush>
+            </Border>
+            <Grid Name="panInfo" Background="#1F000000">
+                <Grid.Effect>
+                    <DropShadowEffect ShadowDepth="0" Opacity="0.85" BlurRadius="3"/>
+                </Grid.Effect>
+                <Label HorizontalAlignment="Left" Margin="38,0,0,65" Name="labTitle" Foreground="#FFFCFCFC" FontSize="13" Padding="2" Height="27" VerticalAlignment="Center" VerticalContentAlignment="Center" />
+                <Path Fill="#FFFCFCFC" Height="20" Margin="12,12,0,0" Stretch="Fill" Width="20" HorizontalAlignment="Left" VerticalAlignment="Top" Name="path" />
+                <TextBlock Name="labContent" Foreground="#FFFCFCFC" VerticalAlignment="Center" Padding="2" HorizontalAlignment="Center" MaxWidth="205" FontSize="14.5" Margin="0,25,0,0" TextWrapping="WrapWithOverflow" TextAlignment="Center" MaxHeight="72" />
+            </Grid>
+        </Grid>
+    </Border>
+    <UserControl.Triggers>
+        <EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
+            <BeginStoryboard>
+                <Storyboard>
+                    <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX" To="0.98" Duration="0:0:0.1" />
+                    <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" To="0.98" Duration="0:0:0.1" />
+                </Storyboard>
+            </BeginStoryboard>
+        </EventTrigger>
+        <EventTrigger RoutedEvent="UIElement.MouseLeave">
+            <BeginStoryboard>
+                <Storyboard>
+                    <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX" To="1" Duration="0:0:0.1" />
+                    <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" To="1" Duration="0:0:0.1" />
+                </Storyboard>
+            </BeginStoryboard>
+        </EventTrigger>
+        <EventTrigger RoutedEvent="UIElement.MouseLeftButtonUp">
+            <BeginStoryboard>
+                <Storyboard>
+                    <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX" To="1" Duration="0:0:0.1" />
+                    <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" To="1" Duration="0:0:0.1" />
+                </Storyboard>
+            </BeginStoryboard>
+        </EventTrigger>
+    </UserControl.Triggers>
+    <UserControl.RenderTransform>
+        <ScaleTransform ScaleX="1" ScaleY="1" CenterX="115" CenterY="60"></ScaleTransform>
+    </UserControl.RenderTransform>
+</UserControl>

File diff suppressed because it is too large
+ 243 - 0
Plain Craft Launcher/Base/InfoBox.xaml.vb


+ 22 - 0
Plain Craft Launcher/Base/ListItem.xaml

@@ -0,0 +1,22 @@
+<UserControl x:Class="ListItem"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             mc:Ignorable="d" 
+             d:DesignHeight="41" d:DesignWidth="211" Height="40" UseLayoutRounding="True">
+    <Grid Name="panBack" UseLayoutRounding="True">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="40" />
+            <ColumnDefinition Width="161*" />
+        </Grid.ColumnDefinitions>
+        <TextBlock Name="labBack" Grid.ColumnSpan="2"  Opacity="0" Background="{DynamicResource ResourceKey=Color3}" />
+        <Label Grid.Column="1" HorizontalAlignment="Left" Name="labMain" Padding="0" Margin="6,0,0,0" FontSize="16" FontWeight="Normal" VerticalAlignment="Top" Foreground="Black" VerticalContentAlignment="Center" Content="主标题" Height="26" />
+        <TextBlock Text="内容" FontSize="12" FontWeight="Normal" HorizontalAlignment="Left" Name="labSub" Padding="0" VerticalAlignment="Bottom" Grid.Column="1" Margin="6,0,0,2" Foreground="#FF707070" />
+        <TextBlock HorizontalAlignment="Stretch" Name="mouseCheck" VerticalAlignment="Stretch" Background="#01005BFF" Grid.ColumnSpan="2" Opacity="0.01" />
+        <Border Grid.Column="1" HorizontalAlignment="Left" Name="border" Width="46" Background="{DynamicResource ResourceKey=Color4}" Opacity="0.7">
+            <Image Name="btnRight" HorizontalAlignment="Center" VerticalAlignment="Center" Width="32" Height="32" Stretch="Fill" IsHitTestVisible="False" Margin="0,0,6,0" />
+        </Border>
+        <Image Name="imgLeft" HorizontalAlignment="Center" VerticalAlignment="Center" Width="32" Height="32" Margin="4" />
+    </Grid>
+</UserControl>

+ 221 - 0
Plain Craft Launcher/Base/ListItem.xaml.vb

@@ -0,0 +1,221 @@
+Public Class ListItem
+
+    Public Event Click(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) '单击事件
+    Public Event Change(ByVal sender As Object, ByVal e As EventArgs) '选择改变事件
+    Public Event ButtonClick(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) '按钮按下事件
+    Public Event IconClick(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) '图标按下事件
+
+#Region "自定义属性"
+
+    Private _CanCheck As Boolean = True
+    Public Property CanCheck As Boolean
+        Get
+            Return _CanCheck
+        End Get
+        Set(ByVal value As Boolean)
+            _CanCheck = value
+            Checked = False
+        End Set
+    End Property '能否选中
+    Private _Checked As Boolean = False
+    Public Property Checked As Boolean
+        Get
+            Return _Checked
+        End Get
+        Set(ByVal value As Boolean)
+            If Not value = _Checked Then '只有变更时才执行
+                _Checked = value
+                If Me.IsLoaded Then '防止默认属性加载时导致错误
+                    RaiseChange()
+                    '同一容器中保证只有1个单选框选中
+                    Dim parentControl As Object = Me.Parent
+                    Dim checkedControl As ArrayList = New ArrayList
+                    Dim checkedCount As Byte = 0
+                    '统计单选框控件与选中个数
+                    For Each con In parentControl.Children
+                        If con.GetType.Name = "ListItem" Then
+                            checkedControl.Add(con)
+                            If con.Checked Then checkedCount = checkedCount + 1
+                        End If
+                    Next
+                    Select Case checkedCount
+                        Case 0 '没有选中项目
+                            If AtLeastCheck Then checkedControl(0).Checked = True
+                        Case Is > 1 '选中项目多于1个
+                            If Me.Checked Then
+                                '如果本控件选中则取消其它所有控件的选中
+                                For Each con As ListItem In checkedControl
+                                    If con.Checked And Not con.Equals(Me) Then con.Checked = False
+                                Next
+                            Else
+                                '如果本控件未选中则只保留第一个选中的控件
+                                checkedCount = False '重复使用变量,实则统计是否为第一个选中控件
+                                For Each con As ListItem In checkedControl
+                                    If con.Checked Then
+                                        If checkedCount Then
+                                            con.Checked = False '修改Checked会自动触发Change事件,所以不用额外触发
+                                        Else
+                                            checkedCount = True
+                                        End If
+                                    End If
+                                Next
+                            End If
+                    End Select
+                End If
+                Refresh()
+            End If
+        End Set
+    End Property '是否选中
+    Private _ShowButton As Boolean = True
+    Public Property ShowButton As Boolean
+        Get
+            Return _ShowButton
+        End Get
+        Set(ByVal value As Boolean)
+            If Not value = _ShowButton Then '只有变更时才执行
+                _ShowButton = value
+                border.Visibility = If(value, Visibility.Visible, Visibility.Hidden)
+                If _ShowButton = False Then ListItem_MouseLeave(Nothing, Nothing)
+            End If
+        End Set
+    End Property '是否显示按钮
+    Public Property MainText As String
+        Get
+            Return labMain.Content
+        End Get
+        Set(ByVal value As String)
+            labMain.Content = value
+        End Set
+    End Property '主内容
+    Public Property SubText As String
+        Get
+            Return labSub.Text
+        End Get
+        Set(ByVal value As String)
+            If value = "" Xor labSub.Text = "" Then
+                If Me.Name = "" Then Me.Name = "ListItem" & GetUUID()
+                If Me.IsLoaded And UseControlAnimation Then
+                    If value = "" Then
+                        AniStart({
+                                 AaY(labMain, (Me.Height - labMain.Height) / 2 - GetTop(labMain), , , New AniEaseEnd)
+                        }, "Li" & Me.Name & "Sub")
+                    Else
+                        AniStart({
+                                 AaY(labMain, -GetTop(labMain), , , New AniEaseEnd)
+                        }, "Li" & Me.Name & "Sub")
+                    End If
+                Else
+                    If value = "" Then
+                        SetTop(labMain, (Me.Height - labMain.Height) / 2)
+                    Else
+                        SetTop(labMain, 0)
+                    End If
+                End If
+            End If
+            labSub.Text = value
+        End Set
+    End Property '副内容
+    Public Property Logo As ImageSource
+        Get
+            Return imgLeft.Source
+        End Get
+        Set(ByVal value As ImageSource)
+            imgLeft.Source = value
+        End Set
+    End Property '图标
+    Public Property LogoSize As Double
+        Get
+            Return imgLeft.Height
+        End Get
+        Set(ByVal value As Double)
+            imgLeft.Height = value
+            imgLeft.Width = value
+        End Set
+    End Property '图标尺寸
+    Public Property ButtonLogo As ImageSource
+        Get
+            Return btnRight.Source
+        End Get
+        Set(ByVal value As ImageSource)
+            btnRight.Source = value
+        End Set
+    End Property '按钮图标
+    Public Property ButtonLogoSize As Double
+        Get
+            Return btnRight.Height
+        End Get
+        Set(ByVal value As Double)
+            btnRight.Height = value
+            btnRight.Width = value
+        End Set
+    End Property '按钮图标尺寸
+    Public Property ButtonBack As Brush
+        Get
+            Return border.Background
+        End Get
+        Set(ByVal value As Brush)
+            border.Background = value
+        End Set
+    End Property '按钮背景颜色
+    Public AtLeastCheck As Boolean = True '是否必须有一项被选中
+    Public Version As New MCVersion("")
+
+#End Region
+
+    '自定义事件
+    Public Sub RaiseChange()
+        RaiseEvent Change(Me, Nothing)
+    End Sub '用于其它控件来引发本控件的Change事件
+    Private Sub Refresh()
+        Dim shouldOpacity As Double = 0
+        If Me.IsMouseOver Then shouldOpacity = 0.1
+        If Me._Checked Then shouldOpacity = shouldOpacity / 2 + 0.14
+        If shouldOpacity = labBack.Opacity Then
+            AniStop("Li" & Me.Name & "Refresh")
+            Exit Sub
+        End If
+        AniStart({
+                 AaOpacity(labBack, shouldOpacity - labBack.Opacity, 100)
+        }, "Li" & Me.Name & "Refresh", False)
+    End Sub
+    Private Sub btnRight_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles border.MouseLeftButtonDown
+        RaiseEvent ButtonClick(Me, e)
+    End Sub
+
+    '系统事件
+    '改成MouseDown是因为拖动滚动条后在其上释放鼠标会导致点击
+    Private Sub ListItem_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles mouseCheck.MouseLeftButtonDown
+        If CanCheck Then
+            Me.Checked = True
+            RaiseEvent Click(Me, e)
+        End If
+    End Sub
+    Private Sub imgLeft_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles imgLeft.MouseLeftButtonDown
+        RaiseEvent IconClick(Me, e)
+        ListItem_MouseUp(sender, e)
+    End Sub
+    Private Sub ListItem_SizeChanged(ByVal sender As Object, ByVal e As System.Windows.SizeChangedEventArgs) Handles Me.SizeChanged
+        panBack.Width = Me.Width + 0.51 '防止按钮莫名显示越界
+        SetLeft(border, Me.ActualWidth - 40)
+    End Sub
+    Private Sub ListItem_MouseEnter(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles Me.MouseEnter
+        If ShowButton Then
+            AniStart({
+                    AaX(border, Me.ActualWidth - 81 - border.Margin.Left, 200, , New AniEaseJumpEnd(0.6))
+                }, "Li" & Me.Name & "PointChange", False)
+        End If
+        Refresh()
+    End Sub
+    Private Sub ListItem_MouseLeave(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles Me.MouseLeave
+        If Me.ActualWidth = 0 Then
+            '防止初始化加载
+            Refresh()
+            Exit Sub
+        End If
+        AniStart({
+                AaX(border, Me.ActualWidth - 40 - border.Margin.Left, 150, , New AniEaseStart)
+            }, "Li" & Me.Name & "PointChange", False)
+        Refresh()
+    End Sub
+
+End Class

+ 205 - 0
Plain Craft Launcher/Base/Loading.xaml

@@ -0,0 +1,205 @@
+<UserControl x:Name="panBack" x:Class="Loading"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+                xmlns:sys="clr-namespace:System;assembly=mscorlib" 
+                xmlns:my="clr-namespace:pcl"
+             mc:Ignorable="d" Height="50" Width="50" MinWidth="50" MinHeight="50" MaxWidth="50" MaxHeight="50" BorderBrush="{DynamicResource Color4}">
+    <Grid Width = "50" Height = "50">
+        <Grid.Resources>
+            <!-- Value Converters -->
+
+            <!-- Particle Styling ,must to has RelativeSource -->
+            <SolidColorBrush x:Key = "ParticleBackgroundColor" Color = "Transparent"/>
+            <sys:Double x:Key = "ParticleOpacity">1</sys:Double>
+            <sys:Double x:Key = "ParticleRadius">5</sys:Double>
+
+            <sys:Double x:Key = "StartingPointX">0</sys:Double>
+            <sys:Double x:Key = "StartingPointY">-20</sys:Double>
+
+            <sys:Double x:Key = "RotationPointX">0.5</sys:Double>
+            <sys:Double x:Key = "RotationPointY">0.5</sys:Double>
+
+            <!-- Particle Origin Angles -->
+            <sys:Double x:Key = "ParticleOriginAngleP0">205</sys:Double>
+            <sys:Double x:Key = "ParticleOriginAngleP1">199</sys:Double>
+            <sys:Double x:Key = "ParticleOriginAngleP2">193</sys:Double>
+            <sys:Double x:Key = "ParticleOriginAngleP3">187</sys:Double>
+            <sys:Double x:Key = "ParticleOriginAngleP4">181</sys:Double>
+
+            <Style x:Key = "EllipseStyle" TargetType = "Ellipse">
+                <Setter Property = "Width" Value = "{StaticResource ParticleRadius}"/>
+                <Setter Property = "Height" Value = "{StaticResource ParticleRadius}"/>
+                <Setter Property = "Fill" Value = "{Binding BorderBrush, ElementName=panBack}"/>
+                <Setter Property = "RenderTransformOrigin" Value = "0.5, 0.5"/>
+                <Setter Property = "Opacity" Value = "{StaticResource ParticleOpacity}"/>
+            </Style>
+        </Grid.Resources>
+        <Canvas Width = "1" Height = "1" Margin="0,0,0,0">
+            <Canvas.Triggers>
+                <EventTrigger RoutedEvent = "Canvas.Loaded">
+                    <EventTrigger.Actions>
+
+                        <BeginStoryboard>
+                            <Storyboard BeginTime = "0:0:0.0" Duration = "0:0:6.6" RepeatBehavior = "Forever" TargetName = "p0" TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)">
+                                <DoubleAnimationUsingKeyFrames BeginTime = "00:00:00.000" Duration = "0:0:6.6">
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="300" KeyTime="0:0:1.1"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="600" KeyTime="0:0:2.2"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="900" KeyTime="0:0:3.3"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1200" KeyTime="0:0:4.4"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1500" KeyTime="0:0:5.5"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1800" KeyTime="0:0:6.6"/>
+                                </DoubleAnimationUsingKeyFrames>
+                            </Storyboard>
+                        </BeginStoryboard>
+                        <BeginStoryboard>
+                            <Storyboard BeginTime = "0:0:0.16" Duration = "0:0:6.6" RepeatBehavior = "Forever" TargetName = "p1" TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)">
+                                <DoubleAnimationUsingKeyFrames BeginTime = "00:00:00.000" Duration = "0:0:6.6">
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="300" KeyTime="0:0:1.1"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="600" KeyTime="0:0:2.2"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="900" KeyTime="0:0:3.3"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1200" KeyTime="0:0:4.4"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1500" KeyTime="0:0:5.5"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1800" KeyTime="0:0:6.6"/>
+                                </DoubleAnimationUsingKeyFrames>
+                            </Storyboard>
+                        </BeginStoryboard>
+                        <BeginStoryboard>
+                            <Storyboard BeginTime = "0:0:0.32" Duration = "0:0:6.6" RepeatBehavior = "Forever" TargetName = "p2" TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)">
+                                <DoubleAnimationUsingKeyFrames BeginTime = "00:00:00.000" Duration = "0:0:6.6">
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="300" KeyTime="0:0:1.1"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="600" KeyTime="0:0:2.2"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="900" KeyTime="0:0:3.3"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1200" KeyTime="0:0:4.4"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1500" KeyTime="0:0:5.5"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1800" KeyTime="0:0:6.6"/>
+                                </DoubleAnimationUsingKeyFrames>
+                            </Storyboard>
+                        </BeginStoryboard>
+                        <BeginStoryboard>
+                            <Storyboard BeginTime = "0:0:0.48" Duration = "0:0:6.6" RepeatBehavior = "Forever" TargetName = "p3" TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)">
+                                <DoubleAnimationUsingKeyFrames BeginTime = "00:00:00.000" Duration = "0:0:6.6">
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="300" KeyTime="0:0:1.1"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="600" KeyTime="0:0:2.2"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="900" KeyTime="0:0:3.3"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1200" KeyTime="0:0:4.4"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1500" KeyTime="0:0:5.5"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1800" KeyTime="0:0:6.6"/>
+                                </DoubleAnimationUsingKeyFrames>
+                            </Storyboard>
+                        </BeginStoryboard>
+                        <BeginStoryboard>
+                            <Storyboard BeginTime = "0:0:0.64" Duration = "0:0:6.6" RepeatBehavior = "Forever" TargetName = "p4" TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)">
+                                <DoubleAnimationUsingKeyFrames BeginTime = "00:00:00.000" Duration = "0:0:6.6">
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="300" KeyTime="0:0:1.1"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="600" KeyTime="0:0:2.2"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="900" KeyTime="0:0:3.3"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1200" KeyTime="0:0:4.4"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1500" KeyTime="0:0:5.5"/>
+                                    <SplineDoubleKeyFrame KeySpline="0.75,0.22 0.25,0.78" Value="1800" KeyTime="0:0:6.6"/>
+                                </DoubleAnimationUsingKeyFrames>
+                            </Storyboard>
+                        </BeginStoryboard>
+
+                    </EventTrigger.Actions>
+                </EventTrigger>
+            </Canvas.Triggers>
+            <Border
+  x:Name = "p0"
+  Background = "{StaticResource ParticleBackgroundColor}"
+  Opacity = "{StaticResource ParticleOpacity}">
+                <Border.RenderTransform>
+                    <RotateTransform/>
+                </Border.RenderTransform>
+                <Border.RenderTransformOrigin>
+                    <Point X = "{StaticResource RotationPointX}" Y = "{StaticResource RotationPointY}"/>
+                </Border.RenderTransformOrigin>
+                <Ellipse Style = "{StaticResource EllipseStyle}">
+                    <Ellipse.RenderTransform>
+                        <TransformGroup>
+                            <TranslateTransform X = "{StaticResource StartingPointX}" Y = "{StaticResource StartingPointY}"/>
+                            <RotateTransform Angle = "{StaticResource ParticleOriginAngleP0}"/>
+                        </TransformGroup>
+                    </Ellipse.RenderTransform>
+                </Ellipse>
+            </Border>
+            <Border
+  x:Name = "p1"
+  Background = "{StaticResource ParticleBackgroundColor}"
+  Opacity = "{StaticResource ParticleOpacity}">
+                <Border.RenderTransform>
+                    <RotateTransform/>
+                </Border.RenderTransform>
+                <Border.RenderTransformOrigin>
+                    <Point X = "{StaticResource RotationPointX}" Y = "{StaticResource RotationPointY}"/>
+                </Border.RenderTransformOrigin>
+                <Ellipse Style = "{StaticResource EllipseStyle}">
+                    <Ellipse.RenderTransform>
+                        <TransformGroup>
+                            <TranslateTransform X = "{StaticResource StartingPointX}" Y = "{StaticResource StartingPointY}"/>
+                            <RotateTransform Angle = "{StaticResource ParticleOriginAngleP1}"/>
+                        </TransformGroup>
+                    </Ellipse.RenderTransform>
+                </Ellipse>
+            </Border>
+            <Border
+  x:Name = "p2"
+  Background = "{StaticResource ParticleBackgroundColor}"
+  Opacity = "{StaticResource ParticleOpacity}">
+                <Border.RenderTransform>
+                    <RotateTransform/>
+                </Border.RenderTransform>
+                <Border.RenderTransformOrigin>
+                    <Point X = "{StaticResource RotationPointX}" Y = "{StaticResource RotationPointY}"/>
+                </Border.RenderTransformOrigin>
+                <Ellipse Style = "{StaticResource EllipseStyle}">
+                    <Ellipse.RenderTransform>
+                        <TransformGroup>
+                            <TranslateTransform X = "{StaticResource StartingPointX}" Y = "{StaticResource StartingPointY}"/>
+                            <RotateTransform Angle = "{StaticResource ParticleOriginAngleP2}"/>
+                        </TransformGroup>
+                    </Ellipse.RenderTransform>
+                </Ellipse>
+            </Border>
+            <Border
+  x:Name = "p3"
+  Background = "{StaticResource ParticleBackgroundColor}"
+  Opacity = "{StaticResource ParticleOpacity}">
+                <Border.RenderTransform>
+                    <RotateTransform/>
+                </Border.RenderTransform>
+                <Border.RenderTransformOrigin>
+                    <Point X = "{StaticResource RotationPointX}" Y = "{StaticResource RotationPointY}"/>
+                </Border.RenderTransformOrigin>
+                <Ellipse Style = "{StaticResource EllipseStyle}">
+                    <Ellipse.RenderTransform>
+                        <TransformGroup>
+                            <TranslateTransform X = "{StaticResource StartingPointX}" Y = "{StaticResource StartingPointY}"/>
+                            <RotateTransform Angle = "{StaticResource ParticleOriginAngleP3}"/>
+                        </TransformGroup>
+                    </Ellipse.RenderTransform>
+                </Ellipse>
+            </Border>
+            <Border
+  x:Name = "p4"
+  Background = "{StaticResource ParticleBackgroundColor}"
+  Opacity = "{StaticResource ParticleOpacity}">
+                <Border.RenderTransform>
+                    <RotateTransform/>
+                </Border.RenderTransform>
+                <Border.RenderTransformOrigin>
+                    <Point X = "{StaticResource RotationPointX}" Y = "{StaticResource RotationPointY}"/>
+                </Border.RenderTransformOrigin>
+                <Ellipse Style = "{StaticResource EllipseStyle}">
+                    <Ellipse.RenderTransform>
+                        <TransformGroup>
+                            <TranslateTransform X = "{StaticResource StartingPointX}" Y = "{StaticResource StartingPointY}"/>
+                            <RotateTransform Angle = "{StaticResource ParticleOriginAngleP4}"/>
+                        </TransformGroup>
+                    </Ellipse.RenderTransform>
+                </Ellipse>
+            </Border>
+        </Canvas>
+    </Grid>
+</UserControl>

+ 3 - 0
Plain Craft Launcher/Base/Loading.xaml.vb

@@ -0,0 +1,3 @@
+Public Class Loading
+
+End Class

+ 25 - 0
Plain Craft Launcher/Base/MetroMsgbox.xaml

@@ -0,0 +1,25 @@
+<Window x:Class="MetroMsgbox"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             mc:Ignorable="d" d:DesignHeight="398" d:DesignWidth="738" xmlns:my="clr-namespace:pcl" ResizeMode="NoResize" WindowStyle="None" ShowInTaskbar="False" WindowStartupLocation="CenterOwner" AllowsTransparency="True" Background="#00000000">
+
+        <StackPanel Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Name="panMain" MaxWidth="{Binding RelativeSource={RelativeSource Self}, Path=Parent.Width}" UseLayoutRounding="True" Background="#00FCFCFC">
+        <StackPanel.Effect>
+            <DropShadowEffect Color="Black" BlurRadius="20" ShadowDepth="3" Opacity="{Binding ElementName=shadow, Path=Opacity}"></DropShadowEffect>
+        </StackPanel.Effect>
+        <TextBlock FontSize="23" Foreground="{DynamicResource ResourceKey=Color4}" HorizontalAlignment="Left" MaxWidth="{Binding RelativeSource={RelativeSource Self}, Path=Parent.Width}" Name="labTitle" Padding="27,22,28,0" Text="测试标题文本" TextWrapping="Wrap" VerticalAlignment="Top" UseLayoutRounding="True" />
+        <Rectangle Height="2" Fill="{DynamicResource ResourceKey=Color4}" Margin="0,9,0,13" UseLayoutRounding="True" HorizontalAlignment="Center" Name="line" Width="0" Opacity="0" />
+        <Border MaxWidth="{Binding RelativeSource={RelativeSource Self}, Path=Parent.Width}" Name="panCaption" Padding="28,0,28,20" UseLayoutRounding="True">
+            <TextBlock Text="测试内容文本哇哈哈啊哈到" TextWrapping="Wrap" Name="labCaption" FontSize="15" Foreground="#FF5C5C5C" FontWeight="Normal" />
+        </Border>
+        <StackPanel Name="panBtn" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="150,0,22,22" Orientation="Horizontal" UseLayoutRounding="True">
+                <my:Button ColorType="COMMON" Text="测试按钮1" x:Name="btn1" FontSize="14" Margin="15,0,0,0" TextPadding="7" UseLayoutRounding="True" Padding="0" />
+                <my:Button ColorType="COMMON" Text="测试按钮2" x:Name="btn2" FontSize="14" Margin="15,0,0,0" TextPadding="7" Visibility="Visible" UseLayoutRounding="True" />
+                <my:Button ColorType="COMMON" Text="测试按钮3" x:Name="btn3" FontSize="14" TextPadding="7" Margin="15,0,0,0" Visibility="Visible" UseLayoutRounding="True" />
+            </StackPanel>
+        <Label Height="0" Name="shadow" Opacity="0" />
+    </StackPanel>
+    
+</Window>

+ 97 - 0
Plain Craft Launcher/Base/MetroMsgbox.xaml.vb

@@ -0,0 +1,97 @@
+Public Class MetroMsgbox
+
+    Private MyConverter As MyMsgboxConverter
+
+    Public Sub New(ByVal Converter As MyMsgboxConverter)
+        InitializeComponent()
+        btn1.Name = btn1.Name & GetUUID()
+        btn2.Name = btn2.Name & GetUUID()
+        btn3.Name = btn3.Name & GetUUID()
+        MyConverter = Converter
+        labTitle.Text = Converter.Title
+        labCaption.Text = Converter.Caption
+        btn1.Text = Converter.Button1
+        If Converter.IsWarn Then btn1.ColorType = Button.State.RED
+        btn2.Text = Converter.Button2
+        btn3.Text = Converter.Button3
+        btn2.Visibility = If(Converter.Button2 = "", Visibility.Collapsed, Visibility.Visible)
+        btn3.Visibility = If(Converter.Button3 = "", Visibility.Collapsed, Visibility.Visible)
+    End Sub
+
+    Private Sub Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Loaded
+        Try
+            log("[MsgBox] 显示弹窗:" & labTitle.Text)
+            log("[MsgBox] 弹窗内容:" & Mid(labCaption.Text, 1, 400) & If(Len(labCaption.Text) > 400, "...", ""))
+            If panCaption.ActualWidth = panMain.ActualWidth Then panMain.Width = panMain.ActualWidth + 75
+            panMain.UpdateLayout()
+            '配色初始化
+            If btn2.IsVisible And Not btn1.ColorType = Button.State.RED Then btn1.ColorType = Button.State.HIGHLIGHT
+            '延时执行的动画
+            Me.Name = "frm" & GetUUID()
+            AniStart({
+                AaBackGround(Me, New MyColor(20, 0, 0, 0), 500, , New AniEaseStart),
+                AaBackGround(panMain, New MyColor(255, 0, 0, 0), 300, , New AniEaseStart),
+                AaOpacity(shadow, 0.75, 600, 150),
+                AaWidth(line, panMain.ActualWidth - 40, 350, 150, New AniEaseEnd),
+                AaOpacity(line, 1, 250, 150),
+                AaWidth(labTitle, labTitle.ActualWidth, 250, 200, New AniEaseEnd),
+                AaOpacity(labTitle, 1, 200, 200),
+                AaOpacity(panCaption, 1, 250, 250),
+                AaHeight(btn1, btn1.ActualHeight, 150, 300),
+                AaHeight(btn2, btn2.ActualHeight, 150, 350),
+                AaHeight(btn3, btn3.ActualHeight, 150, 400)
+            }, "MsgboxStart" & Me.Name)
+            '动画初始化
+            panBtn.Height = panBtn.ActualHeight
+            btn1.Height = 0
+            btn2.Height = 0
+            btn3.Height = 0
+            labTitle.Width = 0
+            labTitle.Opacity = 0
+            labTitle.TextWrapping = TextWrapping.NoWrap
+            panCaption.Opacity = 0
+        Catch ex As Exception
+            ExShow(ex, "弹窗显示失败", ErrorLevel.AllUsers)
+        End Try
+    End Sub
+    Private Sub CloseWindow()
+        log("[MsgBox] 关闭弹窗,返回值:" & MyConverter.ReturnCode)
+        AniStart({
+            AaBackGround(Me, New MyColor(-20, 0, 0, 0), 300, , New AniEaseStart),
+            AaBackGround(panMain, New MyColor(-255, 0, 0, 0), 300, 100, New AniEaseStart),
+            AaOpacity(shadow, -0.75, 150),
+            AaWidth(line, -line.ActualWidth, 250, , New AniEaseStart),
+            AaOpacity(line, -1, 150, 100),
+            AaWidth(labTitle, -labTitle.ActualWidth, 250),
+            AaOpacity(labTitle, -1, 200),
+            AaOpacity(panCaption, -1, 200),
+            AaHeight(btn1, -btn1.ActualHeight, 150, 60),
+            AaHeight(btn2, -btn2.ActualHeight, 150, 30),
+            AaHeight(btn3, -btn3.ActualHeight, 150),
+            AaCode({"Close", Me}, , True)
+        }, "MsgboxClose" & Me.Name)
+    End Sub
+
+    Private Sub MetroMsgbox_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Input.KeyEventArgs) Handles Me.KeyDown
+        If e.Key = Key.Enter Then btn1_Click()
+    End Sub
+    Private Sub btn1_Click() Handles btn1.Click
+        If MyConverter.IsExited Then Exit Sub
+        MyConverter.IsExited = True
+        MyConverter.ReturnCode = 1
+        CloseWindow()
+    End Sub
+    Private Sub btn2_Click() Handles btn2.Click
+        If MyConverter.IsExited Then Exit Sub
+        MyConverter.IsExited = True
+        MyConverter.ReturnCode = 2
+        CloseWindow()
+    End Sub
+    Private Sub btn3_Click() Handles btn3.Click
+        If MyConverter.IsExited Then Exit Sub
+        MyConverter.IsExited = True
+        MyConverter.ReturnCode = 3
+        CloseWindow()
+    End Sub
+
+End Class

+ 3957 - 0
Plain Craft Launcher/Base/Modules.vb

@@ -0,0 +1,3957 @@
+Imports System.Drawing.Imaging
+Imports System.Reflection
+Imports System.Security.Cryptography
+Imports System.Windows.Threading
+Imports Ionic.Zip
+
+Public Module Modules
+
+#Region "Review完成 | 声明"
+
+    ''' <summary>
+    ''' 程序的启动路径,以“\”结尾。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public PATH As String = GetPathFromFullPath(System.Windows.Forms.Application.ExecutablePath)
+
+    ''' <summary>
+    ''' 随机数。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Rnd As New Random
+
+    ''' <summary>
+    ''' 正在拖动的滚动条。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public DragingScroll As VScroll = Nothing
+
+    ''' <summary>
+    ''' 位图缓存。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public BitmapCache As New Dictionary(Of String, MyBitmap)
+
+#End Region
+
+#Region "Review完成 | API"
+
+    ''' <summary>
+    ''' 获取指定窗口的句柄。返回查找到的第一个窗口的句柄。
+    ''' </summary>
+    ''' <param name="lpClassName">窗口的类名,使用Spy++可以查看。</param>
+    ''' <param name="lpWindowName">窗口的标题。</param>
+    ''' <returns>查找到的第一个窗口的句柄。</returns>
+    ''' <remarks></remarks>
+    Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Integer
+    ''' <summary>
+    ''' 彻底释放某个句柄对应项目的资源。
+    ''' </summary>
+    ''' <param name="hObject">需要释放资源的句柄。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Declare Function DeleteObject Lib "gdi32" Alias "DeleteObject" (ByVal hObject As IntPtr) As Boolean
+    ''' <summary>
+    ''' 根据句柄设置对应窗口的标题。
+    ''' </summary>
+    ''' <param name="hwnd">窗口句柄。</param>
+    ''' <param name="lpString">要设置的标题。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" (ByVal hwnd As Integer, ByVal lpString As String) As Integer
+    ''' <summary>
+    ''' 获取某个句柄对应的窗口的所属进程ID。
+    ''' </summary>
+    ''' <param name="hwnd">窗口句柄。</param>
+    ''' <param name="lpdwProcessId">获取到的ID。需要变量承载。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Declare Function GetWindowThreadProcessId Lib "user32" Alias "GetWindowThreadProcessId" (ByVal hwnd As Integer, ByVal lpdwProcessId As Integer) As Integer
+    ''' <summary>
+    ''' 设置某个句柄对应的窗口的属性。
+    ''' </summary>
+    ''' <param name="hwnd">窗口句柄。</param>
+    ''' <param name="hWndInsertAfter">置顶标记。-1 为置顶,-2 为不置顶。</param>
+    ''' <param name="x">窗口的 X 坐标。</param>
+    ''' <param name="y">窗口的 Y 坐标。</param>
+    ''' <param name="cx">窗口的宽度。</param>
+    ''' <param name="cy">窗口的高度。</param>
+    ''' <param name="wFlags">利用 Or 连接的参数。
+    ''' SWP_HIDEWINDOW:隐藏窗口。
+    ''' SWP_SHOWWINDOW:显示窗口。
+    ''' SWP_NOMOVE:维持当前位置(忽略 X 和 Y 参数)。
+    ''' SWP_NOSIZE:维持当前尺寸(忽略 cx 和 cy 参数)。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Declare Function SetWindowPos Lib "user32" Alias "SetWindowPos" (ByVal hwnd As Integer, ByVal hWndInsertAfter As Integer, ByVal x As Integer, ByVal y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal wFlags As Integer) As Integer
+    ''' <summary>
+    ''' 播放 BGM。
+    ''' </summary>
+    ''' <param name="lpszSoundName">音乐文件的完整路径。</param>
+    ''' <param name="hModule">填写“0”。</param>
+    ''' <param name="dwFlags">填写“131072 Or 1 Or 8”。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Declare Function PlaySound Lib "winmm.dll" (ByVal lpszSoundName As String, ByVal hModule As Integer, ByVal dwFlags As Integer) As Integer
+    ''' <summary>
+    ''' 播放 MP3 相关项。
+    ''' </summary>
+    ''' <param name="lpstrCommand"></param>
+    ''' <param name="lpstrRetumString"></param>
+    ''' <param name="uReturnLength"></param>
+    ''' <param name="hwndCallback"></param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" (ByVal lpstrCommand As String, ByVal lpstrRetumString As String, ByVal uReturnLength As Integer, ByVal hwndCallback As Integer) As Integer
+
+    Public Const SWP_HIDEWINDOW As Integer = 8 * 16 '0x0080
+    Public Const SWP_SHOWWINDOW As Integer = 4 * 16 '0x0040
+    Public Const SWP_NOMOVE As Integer = 2 '0x0002
+    Public Const SWP_NOSIZE As Integer = 1 '0x0001
+
+#End Region
+
+#Region "类"
+
+    ''' <summary>
+    ''' 支持浮点数与常见类型隐式转换的颜色。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Class MyColor
+
+        '属性
+        Public A As Double = 0
+        Public R As Double = 0
+        Public G As Double = 0
+        Public B As Double = 0
+
+        '类型转换
+        Public Shared Widening Operator CType(ByVal col As Color) As MyColor
+            Return New MyColor(col)
+        End Operator
+        Public Shared Widening Operator CType(ByVal 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(ByVal 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(ByVal bru As SolidColorBrush) As MyColor
+            Return New MyColor(bru.Color)
+        End Operator
+        Public Shared Widening Operator CType(ByVal 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(ByVal bru As Brush) As MyColor
+            Return New MyColor(ColorConverter.ConvertFromString(bru.ToString))
+        End Operator
+        Public Shared Widening Operator CType(ByVal conv As MyColor) As Brush
+            Return New SolidColorBrush(Color.FromArgb(MathByte(conv.A), MathByte(conv.R), MathByte(conv.G), MathByte(conv.B)))
+        End Operator
+
+        '颜色运算
+        Shared Operator +(ByVal a As MyColor, ByVal 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
+        Shared Operator -(ByVal a As MyColor, ByVal 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
+        Shared Operator *(ByVal a As MyColor, ByVal 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
+        Shared Operator /(ByVal a As MyColor, ByVal 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 Sub New()
+        End Sub
+        Public Sub New(ByVal col As Color)
+            Me.A = col.A
+            Me.R = col.R
+            Me.G = col.G
+            Me.B = col.B
+        End Sub
+        Public Sub New(ByVal newA As Double, ByVal col As Color)
+            Me.A = newA
+            Me.R = col.R
+            Me.G = col.G
+            Me.B = col.B
+        End Sub
+        Public Sub New(ByVal newR As Double, ByVal newG As Double, ByVal newB As Double)
+            Me.A = 255
+            Me.R = newR
+            Me.G = newG
+            Me.B = newB
+        End Sub
+        Public Sub New(ByVal newA As Double, ByVal newR As Double, ByVal newG As Double, ByVal newB As Double)
+            Me.A = newA
+            Me.R = newR
+            Me.G = newG
+            Me.B = newB
+        End Sub
+
+        '复写
+        Public Shadows Function toString() As String
+            Return "(" & A & "," & R & "," & G & "," & B & ")"
+        End Function
+
+    End Class
+
+    Public Class AdvancedRect
+
+        '属性
+        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(ByVal left As Double, ByVal top As Double, ByVal width As Double, ByVal height As Double)
+            Me.Left = left
+            Me.Top = top
+            Me.Width = width
+            Me.Height = height
+        End Sub
+
+    End Class '浮点矩形
+    Public Class FileSize
+
+        Public Value As Integer = 0
+
+        '类型转换
+        Public Shared Widening Operator CType(ByVal value As Long) As FileSize
+            Return New FileSize(value)
+        End Operator
+        Public Shared Widening Operator CType(ByVal value As FileSize) As Long
+            Return value.Value
+        End Operator
+        Public Shared Widening Operator CType(ByVal value As Integer) As FileSize
+            Return New FileSize(value)
+        End Operator
+        Public Shared Widening Operator CType(ByVal value As FileSize) As Integer
+            Return value.Value
+        End Operator
+
+        '运算
+        Shared Operator +(ByVal a As FileSize, ByVal b As FileSize) As FileSize
+            Return a.Value + b.Value
+        End Operator
+        Shared Operator -(ByVal a As FileSize, ByVal b As FileSize) As FileSize
+            Return a.Value - b.Value
+        End Operator
+        Shared Operator *(ByVal a As FileSize, ByVal b As FileSize) As Integer
+            Return a.Value * b.Value
+        End Operator
+        Shared Operator /(ByVal a As FileSize, ByVal b As FileSize) As Double
+            Return MathRange(a.Value / b.Value, 0)
+        End Operator
+        Shared Operator =(ByVal a As FileSize, ByVal b As FileSize) As Boolean
+            Return a.Value = b.Value
+        End Operator
+        Shared Operator <>(ByVal a As FileSize, ByVal b As FileSize) As Boolean
+            Return Not a.Value = b.Value
+        End Operator
+        Shared Operator >(ByVal a As FileSize, ByVal b As FileSize) As Boolean
+            Return a.Value > b.Value
+        End Operator
+        Shared Operator <(ByVal a As FileSize, ByVal b As FileSize) As Boolean
+            Return a.Value < b.Value
+        End Operator
+        Shared Operator &(ByVal a As FileSize, ByVal b As String) As String
+            Return a.ToString & b
+        End Operator
+        Shared Operator &(ByVal a As String, ByVal b As FileSize) As String
+            Return a & b.ToString
+        End Operator
+
+        '构造函数
+        Public Sub New()
+        End Sub
+        Public Sub New(ByVal value As Integer)
+            Me.Value = value
+        End Sub
+
+        '复写
+        Public Shadows Function ToString() As String
+            Select Case Me.Value
+                Case Is < 0
+                    Return "未知"
+                Case Is < 1024
+                    Return Me.Value & "B"
+                Case Is < 1024 ^ 2
+                    Return Math.Round(Me.Value / 1024, 1) & "K"
+                Case Is < 1024 ^ 3
+                    Return Math.Round(Me.Value / 1024 ^ 2, 2) & "M"
+                Case Else
+                    Return Math.Round(Me.Value / 1024 ^ 3, 2) & "G"
+            End Select
+        End Function
+
+    End Class '文件大小
+
+    Public Class Time
+
+        Private Property Value As Integer = 0
+
+        '类型转换
+        Public Shared Widening Operator CType(ByVal value As Integer) As Time
+            Return New Time(value)
+        End Operator
+        Public Shared Widening Operator CType(ByVal value As Time) As Integer
+            Return value.Value
+        End Operator
+        Public Shared Widening Operator CType(ByVal value As Int64) As Time
+            Return New Time(value)
+        End Operator
+        Public Shared Widening Operator CType(ByVal value As Time) As Int64
+            Return value.Value
+        End Operator
+
+        '运算
+        Shared Operator +(ByVal a As Time, ByVal b As Time) As Time
+            Return a.Value + b.Value
+        End Operator
+        Shared Operator -(ByVal a As Time, ByVal b As Time) As Time
+            Return a.Value - b.Value
+        End Operator
+        Shared Operator *(ByVal a As Time, ByVal b As Time) As Integer
+            Return a.Value * b.Value
+        End Operator
+        Shared Operator /(ByVal a As Time, ByVal b As Time) As Double
+            Return MathRange(a.Value / b.Value, 0)
+        End Operator
+        Shared Operator =(ByVal a As Time, ByVal b As Time) As Boolean
+            Return a.Value = b.Value
+        End Operator
+        Shared Operator <>(ByVal a As Time, ByVal b As Time) As Boolean
+            Return Not a.Value = b.Value
+        End Operator
+        Shared Operator >(ByVal a As Time, ByVal b As Time) As Boolean
+            Return a.Value > b.Value
+        End Operator
+        Shared Operator <(ByVal a As Time, ByVal b As Time) As Boolean
+            Return a.Value < b.Value
+        End Operator
+        Shared Operator &(ByVal a As Time, ByVal b As String) As String
+            Return a.toString & b
+        End Operator
+        Shared Operator &(ByVal a As String, ByVal b As Time) As String
+            Return a & b.toString
+        End Operator
+
+        '构造函数
+        Public Sub New()
+        End Sub
+        Public Sub New(ByVal value As Integer)
+            Me.Value = value
+        End Sub
+
+        '复写
+        Public Shadows Function toString() As String
+            Dim left As Integer = Me.Value
+            Dim a As Integer
+            a = Math.Floor(left / 3600)
+            toString = FillLength(a.ToString, "0", 2)
+            left = left - a * 3600
+            a = Math.Floor(left / 60)
+            toString = toString & ":" & FillLength(a.ToString, "0", 2) & ":" & FillLength((left - a * 60), "0", 2)
+        End Function
+
+    End Class '时间
+
+#End Region
+
+#Region "控件"
+
+    ''' <summary>
+    ''' 获取控件或窗体的左边距。无视Alignment。
+    ''' </summary>
+    ''' <param name="control"></param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function GetLeft(ByVal control) As Double
+        GetLeft = 0
+        If control.GetType.BaseType.Name.Equals("Window") Then
+            Return control.Left
+        Else
+            Select Case control.HorizontalAlignment
+                Case HorizontalAlignment.Left, HorizontalAlignment.Stretch, HorizontalAlignment.Center
+                    Return control.Margin.Left
+                Case HorizontalAlignment.Right
+                    Return CType(control.Parent, Object).ActualWidth - control.ActualWidth - control.Margin.Right
+                    'Case HorizontalAlignment.Stretch
+                    '    Dim MaxWidth As Double = CType(control.Parent, Object).ActualWidth - control.Margin.Left - control.Margin.Right
+                    '    Return If(control.ActualWidth >= MaxWidth, control.Margin.Left, control.Margin.Left + (MaxWidth - control.ActualWidth) / 2)
+                    'Case HorizontalAlignment.Center
+                    '    Dim CenterPos As Double = control.Margin.Left + (CType(control.Parent, Object).ActualWidth - control.Margin.Left - control.Margin.Right) / 2
+                    '    Return CenterPos - control.ActualWidth / 2
+            End Select
+        End If
+    End Function
+    ''' <summary>
+    ''' 获取控件或窗体的顶边距。无视Alignment。
+    ''' </summary>
+    ''' <param name="control"></param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function GetTop(ByVal control) As Double
+        GetTop = 0
+        If control.GetType.BaseType.Name.Equals("Window") Then
+            Return control.Top
+        Else
+            Select Case control.VerticalAlignment
+                Case VerticalAlignment.Top, VerticalAlignment.Stretch, VerticalAlignment.Center
+                    Return control.Margin.Top
+                Case VerticalAlignment.Bottom
+                    Return -control.Margin.Bottom
+                    'Return CType(control.Parent, Object).ActualHeight - control.ActualHeight - control.Margin.Bottom
+                    'Case VerticalAlignment.Stretch
+                    '    Dim MaxHeight As Double = CType(control.Parent, Object).ActualHeight - control.Margin.Top - control.Margin.Bottom
+                    '    Return If(control.ActualHeight >= MaxHeight, control.Margin.Top, control.Margin.Top + (MaxHeight - control.ActualHeight) / 2)
+                    'Case VerticalAlignment.Center
+                    '    Dim CenterPos As Double = control.Margin.Top + (CType(control.Parent, Object).ActualHeight - control.Margin.Top - control.Margin.Bottom) / 2
+                    '    Return CenterPos - control.ActualHeight / 2
+            End Select
+        End If
+    End Function
+
+    ''' <summary>
+    ''' 将一个IList中的内容添加到另外一个IList。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Sub IListCopy(ByVal list As IList, ByVal arr As IList)
+        For Each item In arr
+            list.Add(item)
+        Next
+    End Sub
+
+    Public Sub SetLeft(ByVal control As Object, ByVal newValue As Double)
+        If Double.IsNaN(newValue) Or Double.IsInfinity(newValue) Then Exit Sub '安全性检查
+
+        If control.GetType.BaseType.Name.Equals("Window") Then
+            control.Left = newValue
+        Else
+            Select Case control.HorizontalAlignment
+                Case HorizontalAlignment.Left, HorizontalAlignment.Stretch, HorizontalAlignment.Center
+                    control.Margin = New Thickness(newValue, control.Margin.Top, control.Margin.Right, control.Margin.Bottom)
+                Case HorizontalAlignment.Right
+                    control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, CType(control.Parent, Object).ActualWidth - control.ActualWidth - newValue, control.Margin.Bottom)
+            End Select
+        End If
+    End Sub '设置Left
+    Public Sub SetTop(ByVal control As Object, ByVal newValue As Double)
+        If Double.IsNaN(newValue) Or Double.IsInfinity(newValue) Then Exit Sub '安全性检查
+
+        If control.GetType.BaseType.Name.Equals("Window") Then
+            control.Top = newValue
+        Else
+            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 If
+    End Sub '设置Top
+
+    ''' <summary>
+    ''' 拖动滚动条。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Sub ControlStartRun()
+        Dim timer As New DispatcherTimer()
+        timer.Interval = TimeSpan.FromMilliseconds(20)
+        AddHandler timer.Tick, Sub()
+
+                                   If IsNothing(DragingScroll) Then Exit Sub
+
+                                   If Mouse.LeftButton = MouseButtonState.Pressed Then
+                                       DragingScroll.Draging(Mouse.GetPosition(DragingScroll.btnDrag).Y)
+                                   Else
+                                       '鼠标放下
+                                       If Not DragingScroll.IsMouseOver Then DragingScroll.DragMouseLeave()
+                                       DragingScroll = Nothing
+                                   End If
+
+                               End Sub
+        timer.Start()
+    End Sub
+
+#End Region
+
+#Region "数学"
+
+    ''' <summary>
+    ''' 获取两数间的百分比。小数点精确到6位。
+    ''' </summary>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function MathPercent(ByVal ValueA As Double, ByVal ValueB As Double, ByVal Percent As Object) As Double
+        Return Math.Round(ValueA * (1 - Percent) + ValueB * Percent, 6) '解决Double计算错误
+    End Function
+    ''' <summary>
+    ''' 获取两颜色间的百分比,根据RGB计算。小数点精确到6位。
+    ''' </summary>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function MathPercent(ByVal ValueA As MyColor, ByVal ValueB As MyColor, ByVal Percent As Object) As MyColor
+        Return MathRound(ValueA * (1 - Percent) + ValueB * Percent, 6) '解决Double计算错误
+    End Function
+
+    ''' <summary>
+    ''' 检查一个数字是否正确(如检查无限、NaN等)。
+    ''' </summary>
+    ''' <param name="Num">需要检查的数字。</param>
+    ''' <returns>是否正确。</returns>
+    ''' <remarks></remarks>
+    Public Function MathCheck(ByVal Num As Double) As Boolean
+        Return Not (Double.IsInfinity(Num) Or Double.IsNaN(Num))
+    End Function
+
+    '贝塞尔曲线计算
+    Public Function MathBezier(ByVal t As Double, ByVal x1 As Double, ByVal y1 As Double, ByVal x2 As Double, ByVal y2 As Double) As Double
+        Dim a, b
+        a = t
+        Do
+            b = 3 * a * ((1 / 3 + x1 - x2) * a * a + (x2 - 2 * x1) * a + x1)
+            a = a + (t - b) * 0.5
+        Loop Until Math.Abs(b - t) < 0.01 '精度1%
+        Return 3 * a * ((1 / 3 + y1 - y2) * a * a + (y2 - 2 * y1) * a + y1)
+    End Function
+    'Byte合理化
+    Public Function MathByte(ByVal d As Double) As Byte
+        If d < 0 Then d = 0
+        If d > 255 Then d = 255
+        Return Math.Round(d)
+    End Function
+    '提供扩展类支持的Round
+    Public Function MathRound(ByVal col As MyColor, Optional ByVal 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
+    '把数值限定范围
+    Public Function MathRange(ByVal value As Double, ByVal min As Double, Optional ByVal max As Double = Double.MaxValue) As Double
+        Return Math.Max(min, Math.Min(max, value))
+    End Function
+    'Byte类溢出处理
+    Public Function ByteOverflow(ByVal int As Integer) As Byte
+        Dim Original = int
+        Dim ReturnValue As Byte
+        Try
+            If int < 0 Then
+                Do Until int >= 0
+                    int = int + 256
+                Loop
+                ReturnValue = int
+            Else
+                ReturnValue = int Mod 256
+            End If
+            Return ReturnValue
+        Catch ex As Exception
+            ExShow(ex, "Byte 溢出处理出错:" & int)
+            Return 0
+        End Try
+    End Function
+
+#End Region
+
+#Region "文本"
+    '填充长度
+    Public Function FillLength(ByVal str As String, ByVal code As String, ByVal length As Byte)
+        If (Len(str) > length) Then Return Mid(str, 1, length)
+        Return Mid(str.Replace(" ", "-").PadRight(length), str.Length + 1).Replace(" ", code) & str
+    End Function
+    '选择其一
+    Public Function OneOf(ByVal objects As Array)
+        Return objects.GetValue(RandomInteger(0, objects.Length - 1))
+    End Function
+    Public Function OneOf(ByVal objects As ArrayList)
+        Return objects(RandomInteger(0, objects.Count - 1))
+    End Function
+    '正则搜索
+    Public Function RegexSearch(ByVal str As String, ByVal regex As String) As ArrayList
+        Try
+            RegexSearch = New ArrayList
+            Dim RegexSearchRes = (New RegularExpressions.Regex(regex)).Matches(str)
+            If IsNothing(RegexSearchRes) Then Return RegexSearch
+            For Each item As RegularExpressions.Match In RegexSearchRes
+                RegexSearch.Add(item.Value)
+            Next
+        Catch
+            Return New ArrayList
+        End Try
+    End Function
+    '加密解密
+    Public Function SerAdd(ByVal str As String) As String
+        Dim text As Byte() = Encoding.Unicode.GetBytes(str)
+        '-----------------------------------------------1
+        Array.Reverse(text)
+        '-----------------------------------------------2
+        Dim out(text.Length - 1) As Byte
+        For i As Integer = 0 To text.Length - 4 Step 4
+            out(i) = text(i + 2)
+            out(i + 1) = text(i + 1)
+            out(i + 2) = text(i + 3)
+            out(i + 3) = text(i)
+        Next i
+        If text.Length Mod 4 = 2 Then
+            out(text.Length - 2) = text(text.Length - 1)
+            out(text.Length - 1) = text(text.Length - 2)
+        End If
+        text = out
+        '-----------------------------------------------3
+        ReDim out(text.Length - 1)
+        For i As Integer = 0 To text.Length - 3 Step 3
+            out(i) = ByteOverflow(text(i + 2) + 12)
+            out(i + 1) = ByteOverflow(text(i) - 85)
+            out(i + 2) = ByteOverflow(text(i + 1) + 36)
+        Next i
+        Select Case text.Length Mod 3
+            Case 1
+                out(text.Length - 1) = ByteOverflow(text(text.Length - 1) - 52)
+            Case 2
+                out(text.Length - 1) = ByteOverflow(text(text.Length - 1) + 16)
+                out(text.Length - 2) = ByteOverflow(text(text.Length - 2) + 48)
+        End Select
+        text = out
+        '-----------------------------------------------4
+        For i As Integer = 0 To text.Length - 1
+            text(i) = ByteOverflow(text(i) + i)
+        Next i
+        '-----------------------------------------------E
+        SerAdd = ""
+        For Each bt As Byte In text
+            SerAdd = SerAdd & BitConverter.ToString({bt})
+        Next
+    End Function
+    Public Function SerRemove(ByVal str As String) As String
+        Dim line1 As String, line2 As String
+        line1 = "这就是解密函数……自己写的……很口胡很辣鸡……做一个加密只是为了防那些脑残熊孩子,既然都能搞到这函数了,那加密也没用,所以就别吐槽这辣鸡加密了吧233"
+        line2 = "by 龙腾猫跃"
+        Dim text(Len(str) / 2 - 1) As Byte
+        For i As Integer = 0 To Len(str) - 2 Step 2
+            text(i / 2) = Byte.Parse(Mid(str, i + 1, 2), Globalization.NumberStyles.AllowHexSpecifier)
+        Next
+        '-----------------------------------------------4
+        For i As Integer = 0 To text.Length - 1
+            text(i) = ByteOverflow(text(i) - i)
+        Next i
+        '-----------------------------------------------3
+        Dim out(text.Length - 1) As Byte
+        For i As Integer = 0 To text.Length - 3 Step 3
+            out(i) = ByteOverflow(text(i + 1) + 85)
+            out(i + 1) = ByteOverflow(text(i + 2) - 36)
+            out(i + 2) = ByteOverflow(text(i) - 12)
+        Next i
+        Select Case text.Length Mod 3
+            Case 1
+                out(text.Length - 1) = ByteOverflow(text(text.Length - 1) + 52)
+            Case 2
+                out(text.Length - 1) = ByteOverflow(text(text.Length - 1) - 16)
+                out(text.Length - 2) = ByteOverflow(text(text.Length - 2) - 48)
+        End Select
+        text = out
+        '-----------------------------------------------2
+        ReDim out(text.Length - 1)
+        For i As Integer = 0 To text.Length - 4 Step 4
+            out(i) = text(i + 3)
+            out(i + 1) = text(i + 1)
+            out(i + 2) = text(i)
+            out(i + 3) = text(i + 2)
+        Next i
+        If text.Length Mod 4 = 2 Then
+            out(text.Length - 2) = text(text.Length - 1)
+            out(text.Length - 1) = text(text.Length - 2)
+        End If
+        text = out
+        '-----------------------------------------------1
+        Array.Reverse(text)
+        '-----------------------------------------------E
+        SerRemove = Encoding.Unicode.GetString(text)
+    End Function
+    ''' <summary>
+    ''' 从用户输入的字符串中提取数字
+    ''' </summary>
+    ''' <param name="str">用户输入的字符串</param>
+    ''' <returns>字符串中的数字,没有任何数字返回0</returns>
+    ''' <remarks></remarks>
+    Public Function GetDoubleFromString(ByVal str As String) As Double
+        Dim regResult As ArrayList = RegexSearch(str, "(-?[0-9]\d*)?\.?[0-9]\d*")
+        If regResult.Count = 0 Then
+            Return 0
+        Else
+            Return Val(regResult(0))
+        End If
+    End Function
+    '把Exception转为字符串
+    Public Function GetStringFromException(ByVal ex As Exception, Optional ByVal isLong As Boolean = False) As String
+        '构造基本信息与InnerException信息
+        GetStringFromException = ex.Message.Replace(vbCrLf, "")
+        If Not IsNothing(ex.InnerException) Then
+            GetStringFromException = GetStringFromException & vbCrLf & "InnerException: " & ex.InnerException.Message.Replace(vbCrLf, "")
+            If Not IsNothing(ex.InnerException.InnerException) Then
+                GetStringFromException = GetStringFromException & vbCrLf & "InnerException: " & ex.InnerException.InnerException.Message.Replace(vbCrLf, "")
+            End If
+        End If
+        '添加堆栈信息
+        If isLong Then
+            GetStringFromException = GetStringFromException & vbCrLf
+            For Each Stack As String In ex.StackTrace.Split(vbCrLf)
+                If Stack.Contains("pcl.") Then GetStringFromException = GetStringFromException & vbCrLf & Stack
+            Next
+        End If
+        '清除双回车
+        GetStringFromException = GetStringFromException.Replace(vbCrLf & vbCrLf, vbCrLf).Replace(vbCrLf & vbCrLf, vbCrLf)
+    End Function
+#End Region
+
+#Region "Review完成 | 图片"
+
+    ''' <summary>
+    ''' 裁剪一个 BitmapSource。
+    ''' </summary>
+    ''' <param name="img">源图片。</param>
+    Public Function CutBitmap(ByVal img As BitmapSource, ByVal x As Integer, ByVal y As Integer, ByVal width As Integer, ByVal height As Integer) As BitmapSource
+        Try
+            Return New CroppedBitmap(img, New Int32Rect(x, y, width, height))
+        Catch ex As Exception
+            ExShow(ex, "裁剪图片失败", ErrorLevel.AllUsers)
+            Return Nothing
+        End Try
+    End Function
+
+#End Region
+
+#Region "文件"
+
+    ''' <summary>
+    ''' 从应用资源释放文件。
+    ''' </summary>
+    ''' <param name="FileName">需要释放的文件在应用资源中的名称。</param>
+    ''' <param name="OutputPath">释放的路径,包含文件名。</param>
+    ''' <remarks></remarks>
+    Public Sub OutputFileInResource(ByVal FileName As String, ByVal OutputPath As String, ByVal PassOnExists As Boolean)
+        Try
+            Dim Res = My.Resources.ResourceManager.GetObject(FileName)
+            If IsNothing(Res) Then Throw New FileNotFoundException("没有找到名为" & FileName & "的资源")
+            If PassOnExists And File.Exists(OutputPath) Then
+                If GetFileSize(OutputPath) = Res.Length Then Exit Sub
+            End If
+            Directory.CreateDirectory(Mid(OutputPath, 1, OutputPath.LastIndexOf("\")))
+            Using OutputStream As New FileStream(OutputPath, FileMode.OpenOrCreate, FileAccess.Write)
+                OutputStream.Write(Res, 0, Res.Length)
+                OutputStream.Flush()
+            End Using
+            log("[System] 释放文件:" & FileName)
+        Catch ex As Exception
+            ExShow(ex, "释放文件失败", ErrorLevel.DebugOnly)
+            Throw ex
+        End Try
+    End Sub
+
+    ''' <summary>
+    ''' 解压文件。
+    ''' </summary>
+    ''' <param name="SourceFile">需要解压的文件的完整路径。</param>
+    ''' <param name="UnzipDir">解压的目标文件夹。</param>
+    ''' <param name="RunThread">解压结束后(无论成功还是失败)执行的方法。</param>
+    ''' <remarks></remarks>
+    Public Sub UnzipFile(ByVal SourceFile As String, ByVal UnzipDir As String, Optional ByVal RunThread As ParameterizedThreadStart = Nothing)
+        Dim th As New Thread(Sub()
+                                 Try
+                                     Directory.CreateDirectory(UnzipDir)
+                                     Using zip As New ZipFile(SourceFile)
+                                         zip.ExtractAll(UnzipDir)
+                                     End Using
+                                 Catch ex As Exception
+                                     ExShow(ex, "解压文件失败:" & SourceFile)
+                                 End Try
+                                 If Not IsNothing(RunThread) Then RunThread.Method.Invoke(Nothing, {Nothing})
+                             End Sub)
+        th.Start()
+    End Sub '解压ZIP文件
+
+    ''' <summary>
+    ''' 弹出选取文件对话框并且要求选取文件。
+    ''' </summary>
+    ''' <param name="FileFilter">要求的格式。如:“常用图片文件(*.png;*.jpeg)|*.png;*.jpg;*.jpeg”。</param>
+    ''' <param name="Title">弹窗的标题。</param>
+    ''' <returns>选择的唯一文件的完整路径,如果没有选择可能为空字符串。</returns>
+    ''' <remarks></remarks>
+    Public Function SelectFile(ByVal FileFilter As String, ByVal Title As String) As String
+        Using fileDialog As New Forms.OpenFileDialog
+            fileDialog.AddExtension = True
+            fileDialog.AutoUpgradeEnabled = True
+            fileDialog.CheckFileExists = True
+            fileDialog.Filter = FileFilter
+            fileDialog.Multiselect = False
+            fileDialog.Title = Title
+            fileDialog.ShowDialog()
+            Return fileDialog.FileName
+        End Using
+    End Function
+
+    ''' <summary>
+    ''' 弹出选取文件夹对话框并且要求选取文件夹。
+    ''' </summary>
+    ''' <param name="Title">弹窗的标题。</param>
+    ''' <returns>选择的唯一文件夹的完整路径,如果没有选择可能为空字符串。</returns>
+    ''' <remarks></remarks>
+    Public Function SelectFolder(ByVal Title As String) As String
+        Using folderDialog As New Forms.FolderBrowserDialog
+            folderDialog.Description = Title
+            folderDialog.ShowNewFolderButton = True
+            folderDialog.ShowDialog()
+            Return folderDialog.SelectedPath
+        End Using
+    End Function
+
+    ''' <summary>
+    ''' 从文件路径或者URL获取不包含文件名的路径。不包含路径将会抛出异常。
+    ''' </summary>
+    ''' <param name="FilePath">文件路径,以标识符结尾。如果不包含文件名则必须以标识符结尾。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function GetPathFromFullPath(ByVal FilePath As String) As String
+        If Not (FilePath.Contains("\") Or FilePath.Contains("/")) Then Throw New FileFormatException("不包含路径:" & FilePath)
+        If FilePath.EndsWith("\") Or FilePath.EndsWith("/") Then Return FilePath
+        GetPathFromFullPath = Left(FilePath, FilePath.LastIndexOfAny({"\", "/"}) + 1)
+        If GetPathFromFullPath = "" Then Throw New FileFormatException("不包含路径:" & FilePath)
+    End Function
+
+    ''' <summary>
+    ''' 从文件路径或者URL获取不包含路径的文件名。不包含文件名将会抛出异常。
+    ''' </summary>
+    ''' <param name="FilePath">文件路径。可以不完整。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function GetFileNameFromPath(ByVal FilePath As String) As String
+        If FilePath.EndsWith("\") Or FilePath.EndsWith("/") Then Throw New FileFormatException("不包含文件名:" & FilePath)
+        If Not (FilePath.Contains("\") Or FilePath.Contains("/")) Then Return FilePath
+        GetFileNameFromPath = Mid(FilePath, FilePath.LastIndexOfAny({"\", "/"}) + 2)
+        If GetFileNameFromPath.Contains("\\") And GetFileNameFromPath.Contains("?") Then
+            '包含网络参数
+            GetFileNameFromPath = Left(GetFileNameFromPath, FilePath.LastIndexOf("?"))
+        End If
+        If GetFileNameFromPath = "" Then Throw New FileFormatException("不包含文件名:" & FilePath)
+    End Function
+
+    ''' <summary>
+    ''' 获取文件的版本信息。
+    ''' </summary>
+    ''' <param name="Path">文件目录。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function GetFileVersion(ByVal Path As String) As String
+        Try
+            Return Diagnostics.FileVersionInfo.GetVersionInfo(Path).FileVersion
+        Catch ex As Exception
+            ExShow(ex, "获取文件版本失败:" & Path, ErrorLevel.Slient)
+            Return ""
+        End Try
+    End Function
+
+    ''' <summary>
+    ''' 获取一个文件夹下的所有文件的完整路径。
+    ''' </summary>
+    ''' <param name="SourcePath">源文件夹,不以“\”结尾。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function GetFilesFromPath(ByVal SourcePath As String) As ArrayList
+        GetFilesFromPath = New ArrayList
+        GetFilesFromPath(SourcePath, GetFilesFromPath)
+    End Function
+    Private Function GetFilesFromPath(ByVal SourcePath As String, ByRef CurrentList As ArrayList) As ArrayList
+        Try
+            If Not Directory.Exists(SourcePath) Then Return New ArrayList
+            '列举文件
+            For Each File As FileInfo In (New DirectoryInfo(SourcePath)).GetFiles
+                CurrentList.Add(File.FullName)
+            Next
+            '列举目录
+            For Each Folder As DirectoryInfo In (New DirectoryInfo(SourcePath)).GetDirectories
+                GetFilesFromPath(Folder.FullName, CurrentList)
+            Next
+        Catch ex As Exception
+            ExShow(ex, "列举文件失败:" & SourcePath, ErrorLevel.Slient)
+        End Try
+        '返回
+        Return CurrentList
+    End Function
+
+    '获取文件大小(HTTP URL或文件路径)
+    Public Function GetFileSize(ByVal Path As String, Optional ByRef FileMission As WebFile = Nothing) As Integer
+        Try
+            If Path.StartsWith("http") Then
+                If MODE_OFFLINE Then Return 0
+                '获取网络文件大小
+                Dim Res As Net.WebRequest = Net.WebRequest.Create(Path)
+                Dim Response As WebResponse = Res.GetResponse
+                Dim Size As Integer = Response.ContentLength()
+                If Not IsNothing(FileMission) Then
+                    '用Ref返回实际地址
+                    If Not FileMission.AllWebAddress(0) = Response.ResponseUri.ToString Then
+                        If FileMission.RefreshName Then FileMission.LocalName = GetFileNameFromPath(Response.ResponseUri.ToString)
+                        FileMission.AllWebAddress(0) = Response.ResponseUri.ToString
+                    End If
+                End If
+                '释放资源
+                Response.Close()
+                Res.Abort()
+                Return Size
+            Else
+                If File.Exists(Path) Then
+                    Return New FileInfo(Path).Length
+                Else
+                    Return 0
+                End If
+            End If
+        Catch ex As Exception
+            ExShow(ex, "获取文件大小失败:" & Path, ErrorLevel.Slient)
+            Return 0
+        End Try
+    End Function
+    '获取MD5
+    Public Function GetFileMD5(ByVal filepath As String) As String
+        Try
+            '网上抄的啊,自己改了改而已XD
+            Dim result As String = ""
+            Using fstream As New FileStream(filepath, FileMode.Open, FileAccess.Read)
+                Dim dataToHash(fstream.Length - 1) As Byte
+                fstream.Read(dataToHash, 0, fstream.Length)
+                Dim hashvalue As Byte() = CType(CryptoConfig.CreateFromName("MD5"), HashAlgorithm).ComputeHash(dataToHash)
+                Dim i As Double
+                For i = 0 To hashvalue.Length - 1
+                    result += FillLength(Hex(hashvalue(i)).ToLower, "0", 2)
+                Next
+            End Using
+            Return result
+        Catch ex As Exception
+            ExShow(ex, "获取文件MD5失败:" & filepath, ErrorLevel.Slient)
+            Return ""
+        End Try
+    End Function
+    '读取文件
+    Public Function ReadFileToEnd(ByVal filePath As String, Optional ByVal encode As Encoding = Nothing, Optional ByVal isFullPath As Boolean = True) As String
+        Try
+            Dim actualPath As String = If(isFullPath, "", PATH) & filePath
+            If File.Exists(actualPath) Then
+                encode = If(IsNothing(encode), New UTF8Encoding(False), encode)
+                Using reader As New StreamReader(actualPath, encode)
+                    ReadFileToEnd = reader.ReadToEnd
+                End Using
+            Else
+                Return ""
+            End If
+        Catch ex As Exception
+            ExShow(ex, "读取文件出错:" & filePath)
+            Return ""
+        End Try
+    End Function
+    '写入文件
+    Public Sub WriteFile(ByVal filePath As String, ByVal text As String, Optional ByVal add As Boolean = False, Optional ByVal encode As Encoding = Nothing, Optional ByVal isFullPath As Boolean = True)
+        Try
+            Dim actualpath As String = If(isFullPath, "", PATH) & filePath
+            If Not File.Exists(actualpath) Then
+                Directory.CreateDirectory(GetPathFromFullPath(actualpath))
+                File.Create(actualpath).Dispose()
+            End If
+            encode = If(IsNothing(encode), New UTF8Encoding(False), encode)
+            Using writer As New StreamWriter(actualpath, add, encode)
+                writer.Write(text)
+                writer.Flush()
+            End Using
+        Catch ex As Exception
+            ExShow(ex, "写入文件时出错:" & filePath)
+        End Try
+    End Sub
+
+    Private Class IniCache
+        Public content As String = ""
+        Public cache As New Dictionary(Of String, String)
+    End Class
+    Private CacheIni As New Dictionary(Of String, IniCache)
+    ''' <summary>
+    ''' 获取Ini文件内容,这可能会使用到缓存。
+    ''' </summary>
+    ''' <param name="FileName">文件路径或简写。简写将会使用“PCL\文件名.ini”作为路径。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Private Function GetIni(ByVal FileName As String) As IniCache
+        If Not FileName.Contains(":\") Then FileName = PATH & "PCL\" & FileName & ".ini"
+        If CacheIni.ContainsKey(FileName) Then
+            '返回缓存中的信息
+            Dim Cache As New IniCache
+            CacheIni.TryGetValue(FileName, Cache)
+            Return Cache '防止ByRef导致缓存变更
+        Else
+            If File.Exists(FileName) Then
+                '返回文件信息并且记入缓存
+                Dim Cache As String = (vbCrLf & ReadFileToEnd(FileName) & vbCrLf).Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, vbCrLf).Replace(vbCrLf & vbCrLf, vbCrLf)
+                Dim Ini As New IniCache With {.content = Cache}
+                CacheIni.Add(FileName, Ini)
+                Return Ini
+            Else
+                '返回空信息
+                Dim Ini As New IniCache With {.content = ""}
+                CacheIni.Add(FileName, Ini)
+                Return Ini
+            End If
+        End If
+    End Function
+    ''' <summary>
+    ''' 读取Ini文件,这可能会使用到缓存。
+    ''' </summary>
+    ''' <param name="FileName">文件路径或简写。简写将会使用“PCL\文件名.ini”作为路径。</param>
+    ''' <param name="Key">键。</param>
+    ''' <param name="DefaultValue">没有找到键时返回的默认值。</param>
+    Public Function ReadIni(ByVal FileName As String, ByVal Key As String, Optional ByVal DefaultValue As String = "") As String
+        Try
+            '获取目前文件
+            Dim NowIni As IniCache = GetIni(FileName)
+            If IsNothing(NowIni) Then Return DefaultValue
+            '使用缓存
+            If NowIni.cache.ContainsKey(Key) Then Return If(NowIni.cache(Key), DefaultValue)
+            '新读取文件
+            If NowIni.content.Contains(vbCrLf & Key & ":") Then
+                Dim Ret As String = Mid(NowIni.content, NowIni.content.IndexOf(vbCrLf & Key & ":") + 3)
+                Ret = If(Ret.Contains(vbCrLf), Mid(Ret, 1, Ret.IndexOf(vbCrLf)), Ret).Replace(Key & ":", "")
+                NowIni.cache.Add(Key, If(Ret = vbLf, "", Ret))
+                Return If(Ret = vbLf, "", Ret)
+            Else
+                Return DefaultValue
+            End If
+        Catch ex As Exception
+            '读取失败
+            Return DefaultValue
+        End Try
+    End Function
+    ''' <summary>
+    ''' 写入ini文件,这会更新缓存。
+    ''' </summary>
+    ''' <param name="FileName">文件路径或简写。简写将会使用“PCL\文件名.ini”作为路径。</param>
+    ''' <param name="Key">键。</param>
+    ''' <param name="Value">值。</param>
+    ''' <remarks></remarks>
+    Public Sub WriteIni(ByVal FileName As String, ByVal Key As String, ByVal Value As String)
+        On Error Resume Next
+        FileName = If(FileName.Contains(":\"), FileName, PATH & "PCL\" & FileName & ".ini")
+        If IsNothing(Value) Then Value = ""
+        Value = Value.Replace(vbCrLf, "")
+        '创建文件夹
+        If Not Directory.Exists(GetPathFromFullPath(FileName)) Then Directory.CreateDirectory(GetPathFromFullPath(FileName))
+        '获取目前文件
+        Dim NowFile As String = GetIni(FileName).content
+        '如果值一样就不处理
+        If NowFile.Contains(vbCrLf & Key & ":" & Value & vbCrLf) Then Exit Sub
+        '处理文件
+        Dim FindResult As String = ReadIni(FileName, Key)
+        If FindResult = "" And Not NowFile.Contains(vbCrLf & Key & ":") Then
+            '不存在这个键
+            NowFile = NowFile & vbCrLf & Key & ":" & Value
+        Else
+            '存在这个键
+            NowFile = NowFile.Replace(vbCrLf & Key & ":" & FindResult & vbCrLf, vbCrLf & Key & ":" & Value & vbCrLf)
+        End If
+        WriteFile(FileName, NowFile)
+        '刷新目前缓存
+        CacheIni(FileName).content = NowFile
+        CacheIni(FileName).cache.Remove(Key)
+        CacheIni(FileName).cache.Add(Key, Value)
+    End Sub
+#End Region
+
+#Region "Review完成 | 网络"
+
+    ''' <summary>
+    ''' 获取网页源代码。
+    ''' </summary>
+    ''' <param name="URL">网页的URL。</param>
+    ''' <param name="Encode">网页的编码,通常为UTF-8。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function GetWebsiteCode(ByVal URL As String, ByVal Encode As Encoding) As String
+        If MODE_OFFLINE Then Return ""
+
+        '初始化
+        Dim req As HttpWebRequest = Nothing
+        Dim res As HttpWebResponse = Nothing
+        Dim strm As StreamReader = Nothing
+        GetWebsiteCode = ""
+
+        Try
+            req = WebRequest.Create(URL)
+            req.Timeout = 6000
+            res = req.GetResponse()
+            strm = New StreamReader(res.GetResponseStream(), Encode)
+            GetWebsiteCode = strm.ReadToEnd
+        Catch ex As Exception
+            If Not URL.Contains("https://sessionserver.mojang.com/session/minecraft/profile/") Then ExShow(ex, "获取网页源代码失败:" & URL)
+        Finally
+            '释放资源
+            If Not IsNothing(strm) Then strm.Dispose()
+            If Not IsNothing(res) Then res.Close()
+            If Not IsNothing(req) Then req.Abort()
+        End Try
+    End Function
+
+    ''' <summary>
+    ''' 从网络中直接下载文件。
+    ''' </summary>
+    ''' <param name="URL">网络URL。</param>
+    ''' <param name="localFile">下载的本地地址。</param>
+    ''' <returns>是否下载成功。</returns>
+    ''' <remarks></remarks>
+    Public Function DownloadFile(ByVal URL As String, ByVal LocalFile As String) As Boolean
+        If MODE_OFFLINE Then Return False
+
+        '初始化
+        Try
+            '建立目录
+            Directory.CreateDirectory(GetPathFromFullPath(LocalFile))
+            '尝试删除原文件
+            File.Delete(LocalFile)
+        Catch ex As Exception
+            ExShow(ex, "预处理文件失败:" & LocalFile)
+            Return False
+        End Try
+
+        '下载
+        Using Client As New WebClient
+            Try
+                Client.DownloadFile(URL, LocalFile)
+                log("[System] 直接下载文件成功:" & URL)
+                Return True
+            Catch ex As Exception
+                ExShow(ex, "直接下载文件失败:" & URL)
+                Return False
+            End Try
+        End Using
+
+    End Function
+
+    ''' <summary>
+    ''' 进行Ping测试。这不会异步进行,失败将直接抛出异常。
+    ''' </summary>
+    ''' <param name="IP">要进行Ping的IP。</param>
+    ''' <param name="MaxPingTime">最大响应时间,单位为毫秒。</param>
+    ''' <returns>Ping用时,单位为毫秒。</returns>
+    ''' <remarks></remarks>
+    Public Function Ping(ByVal IP As String, ByVal MaxPingTime As Integer) As Integer
+        If MODE_OFFLINE Then Throw New WebException("没有网络连接。")
+        Using pin = New System.Net.NetworkInformation.Ping
+            Return pin.Send(IP, MaxPingTime).RoundtripTime
+        End Using
+    End Function
+
+#End Region
+
+#Region "系统"
+
+    ''' <summary>
+    ''' 测试某段代码用时,单位为毫秒/次。
+    ''' </summary>
+    ''' <param name="run">需要测试的代码。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function TimeTest(ByVal run As ThreadStart) As Double
+        Dim StartTime As Long = My.Computer.Clock.TickCount
+        Dim RunCount As Integer = 0
+        Do While True
+            run.Invoke()
+            RunCount = RunCount + 1
+            If My.Computer.Clock.TickCount - StartTime > 1000 Or RunCount > 100000 Then Exit Do
+        Loop
+        Return (My.Computer.Clock.TickCount - StartTime) / RunCount
+    End Function
+
+    ''' <summary>
+    ''' 打开文件(等同于使用“运行”窗口)。
+    ''' </summary>
+    ''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
+    ''' <param name="Arguments">运行参数。</param>
+    ''' <param name="WaitForExit">是否等待该程序结束。默认为False。</param>
+    ''' <remarks></remarks>
+    Public Sub Shell(ByVal FileName As String, Optional ByVal Arguments As String = "", Optional ByVal WaitForExit As Boolean = False)
+        Try
+            Dim Program As New Process
+            Program.StartInfo.Arguments = Arguments
+            Program.StartInfo.FileName = FileName
+            Program.Start()
+            If WaitForExit Then Program.WaitForExit()
+        Catch ex As Exception
+            ExShow(ex, "执行命令失败:" & FileName, ErrorLevel.AllUsers)
+        End Try
+    End Sub
+
+    ''' <summary>
+    ''' 返回一个枚举对应的字符串。
+    ''' </summary>
+    ''' <param name="EnumData">一个已经实例化的枚举类型。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function GetStringFromEnum(ByVal EnumData As Object) As String
+        Return [Enum].GetName(EnumData.GetType, EnumData)
+    End Function
+
+    ''' <summary>
+    ''' 从指定的枚举中查找某字符串对应的枚举项。
+    ''' </summary>
+    ''' <param name="EnumData">源枚举。</param>
+    ''' <param name="Value">对应枚举一项的字符串。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function GetEnumFromString(ByVal EnumData As Object, ByVal Value As String)
+        Return [Enum].Parse(EnumData.GetType, Value)
+    End Function
+
+    ''' <summary>
+    ''' 获取格式类似于“11:08:52.037”的当前时间的字符串。
+    ''' </summary>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function GetTime() As String
+        Return Date.Now.ToLongTimeString & "." & FillLength(Date.Now.Millisecond, "0", 3)
+    End Function
+
+    ''' <summary>
+    ''' 数组去重。
+    ''' </summary>
+    ''' <param name="array"></param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function ArrayNoDouble(ByVal array As Array) As ArrayList
+        Dim ResultArray As New ArrayList
+
+        For i = 0 To UBound(array)
+            For ii = i + 1 To UBound(array)
+                If array(i).Equals(array(ii)) Then GoTo NextElement
+            Next
+            ResultArray.Add(array(i))
+NextElement:
+        Next i
+
+        Return ResultArray
+    End Function
+
+    ''' <summary>
+    ''' 将数组从大到小排序。
+    ''' </summary>
+    ''' <param name="Array">纯数字的ArrayList。</param>
+    ''' <remarks></remarks>
+    Public Sub ArrayBigToSmall(ByRef Array As ArrayList)
+        If Array.Count < 2 Then Exit Sub
+        For i = 1 To Array.Count - 1
+            If Array(i) > Array(i - 1) Then
+                Dim c = Array(i - 1)
+                Array(i - 1) = Array(i)
+                Array(i) = c
+                If i >= 2 Then i = i - 2
+            End If
+        Next i
+    End Sub
+
+    ''' <summary>
+    ''' 强制将一个支持IList接口的对象转换为任意格式的一维数组。
+    ''' </summary>
+    ''' <typeparam name="T">转换后的数组类型。</typeparam>
+    ''' <param name="Source"></param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function ArrayConventer(Of T, B As IList)(ByVal Source As B) As T()
+        Dim Re(Source.Count - 1) As T
+        For i = 0 To Source.Count - 1
+            Re(i) = Source(i)
+        Next
+        Return Re
+    End Function
+
+    ''' <summary>
+    ''' 检查是否拥有某一文件夹的读取权限。如果文件夹不存在,会返回 False。
+    ''' </summary>
+    ''' <param name="Path">检查目录。</param>
+    Public Function CheckDirectoryPermission(ByVal Path As String) As Boolean
+        Try
+            If Path = "" Then Return False
+            If Not Directory.Exists(Path) Then Return False
+            Dim folderCheck As New DirectoryInfo(Path)
+            folderCheck.EnumerateFiles()
+            Return True
+        Catch
+            Return False
+        End Try
+    End Function
+
+    ''' <summary>
+    ''' 获取程序启动参数。
+    ''' </summary>
+    ''' <param name="Name">参数名。</param>
+    ''' <param name="DefaultValue">默认值。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function GetProgramArgument(ByVal Name As String, Optional ByVal DefaultValue As Object = "")
+        Dim AllArguments() As String = Microsoft.VisualBasic.Command.Split(" ")
+        For i = 0 To AllArguments.Length - 1
+            If AllArguments(i) = "-" & Name Then
+                If AllArguments.Length = i + 1 Then Return True
+                If AllArguments(i + 1).StartsWith("-") Then Return True
+                Return AllArguments(i + 1)
+            End If
+        Next
+        Return DefaultValue
+    End Function
+
+    ''' <summary>
+    ''' 获取Json对象。
+    ''' </summary>
+    ''' <param name="Text"></param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function ReadJson(ByVal Text As String) As JObject
+        Try
+            Return CType(Newtonsoft.Json.JsonConvert.DeserializeObject(Text), JObject)
+        Catch ex As Exception
+            ExShow(ex, "Json读取失败:" & Left(Text, 30))
+            Throw New FileFormatException("错误的 Json:" & GetStringFromException(ex, True))
+        End Try
+    End Function
+
+    '执行命令行函数
+    'Public Function RunCMD(ByVal Commands As String, ByVal Wait As Boolean) As String
+    '    Dim myProcess As New Process()
+    '    Dim myProcessStartInfo As New ProcessStartInfo("cmd.exe")
+    '    myProcessStartInfo.UseShellExecute = False
+    '    myProcessStartInfo.RedirectStandardOutput = True
+    '    myProcessStartInfo.CreateNoWindow = True
+    '    myProcessStartInfo.Arguments = "/c """ & Commands & """"
+    '    myProcess.StartInfo = myProcessStartInfo
+    '    myProcess.Start()
+    '    If Wait Then
+    '        myProcess.WaitForExit()
+    '        Dim myStreamReader As IO.StreamReader = myProcess.StandardOutput
+    '        Dim myString As String = myStreamReader.ReadToEnd()
+    '        myProcess.Close()
+    '        myProcess.Dispose()
+    '        Return myString
+    '    Else
+    '        Return ""
+    '    End If
+    'End Function
+    '注册表IO
+    Public Function ReadReg(ByVal Key As String, Optional ByVal DefaultValue As String = "") As String
+        Try
+            Dim parentKey As Microsoft.Win32.RegistryKey, softKey As Microsoft.Win32.RegistryKey
+            parentKey = My.Computer.Registry.CurrentUser
+            softKey = parentKey.OpenSubKey("Software\" & APPLICATION_SHORT_NAME, True)
+            If softKey Is Nothing Then
+                ReadReg = DefaultValue '不存在则返回默认值
+            Else
+                Dim readValue As New System.Text.StringBuilder
+                readValue.AppendLine(softKey.GetValue(Key))
+                Dim value = readValue.ToString.Replace(vbCrLf, "") '去除莫名的回车
+                Return If(value = "", DefaultValue, value) '错误则返回默认值
+            End If
+        Catch ex As Exception
+            ExShow(ex, "读取注册表出错:" & Key, ErrorLevel.Slient)
+            Return DefaultValue
+        End Try
+    End Function
+    Public Sub WriteReg(ByVal Key As String, ByVal Value As String, Optional ByVal ShowException As Boolean = False)
+        Try
+            Dim parentKey As Microsoft.Win32.RegistryKey, softKey As Microsoft.Win32.RegistryKey
+            parentKey = My.Computer.Registry.CurrentUser
+            softKey = parentKey.OpenSubKey("Software\" & APPLICATION_SHORT_NAME, True)
+            If softKey Is Nothing Then softKey = parentKey.CreateSubKey("Software\" & APPLICATION_SHORT_NAME) '如果不存在就创建  
+            softKey.SetValue(Key, Value)
+        Catch ex As Exception
+            ExShow(ex, "写入注册表出错:" & Key, If(ShowException, modMain.ErrorLevel.DebugOnly, modMain.ErrorLevel.Slient))
+            If ShowException Then Throw '如果显示错误就丢一个
+        End Try
+    End Sub
+
+    Private UUID As Integer = 0
+    ''' <summary>
+    ''' 获取一个全程序内不会重复的数字(伪UUID)。
+    ''' </summary>
+    ''' <returns>UUID,一个大于0的有序整数。</returns>
+    ''' <remarks></remarks>
+    Public Function GetUUID() As Integer
+        UUID = UUID + 1
+        Return UUID
+    End Function
+
+#End Region
+
+#Region "Review完成 | 窗体"
+
+    ''' <summary>
+    ''' 等待显示的弹窗。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public WaitingMyMsgbox As ArrayList = If(IsNothing(WaitingMyMsgbox), New ArrayList, WaitingMyMsgbox)
+    ''' <summary>
+    ''' 显示弹窗。
+    ''' </summary>
+    Public Function MyMsgbox(ByVal Converter As MyMsgboxConverter) As Integer
+        Try
+
+            '存入等待列表
+            If Thread.CurrentThread.IsBackground Or Not Thread.CurrentThread.GetApartmentState = ApartmentState.STA Or Not AniRunning Then
+                If IsNothing(WaitingMyMsgbox) Then WaitingMyMsgbox = New ArrayList '初始化
+                WaitingMyMsgbox.Add(Converter)
+                '返回值
+                If Converter.IsWaitExit Then
+                    Do While Converter.ReturnCode = 0
+                        Thread.Sleep(50)
+                    Loop
+                    Return Converter.ReturnCode
+                Else
+                    Return 0
+                End If
+            End If
+
+            '在主线程,显示窗体
+            Dim MsgBox As New MetroMsgbox(Converter) With {.Owner = frmMain, .Width = MAINFORM_WIDTH - 20, .Height = MAINFORM_HEIGHT - 20, .Left = (My.Computer.Screen.WorkingArea.Width - MAINFORM_WIDTH) / 2 + 10, .Top = (My.Computer.Screen.WorkingArea.Height - MAINFORM_HEIGHT) / 2 + 10}
+            If Converter.IsWaitExit Then
+                MsgBox.ShowDialog()
+                Return Converter.ReturnCode
+            Else
+                MsgBox.Show()
+                Return 0
+            End If
+
+        Catch ex As Exception
+            ExShow(ex, "显示弹窗失败:" & Converter.Title, ErrorLevel.MsgboxAndFeedback)
+            Return 0
+        End Try
+    End Function
+    ''' <summary>
+    ''' 显示弹窗。
+    ''' </summary>
+    ''' <param name="Title">弹窗的标题。</param>
+    ''' <param name="Caption">弹窗的内容。</param>
+    ''' <param name="Button1">显示的第一个按钮,默认为“确定”。</param>
+    ''' <param name="Button2">显示的第二个按钮,默认为空。</param>
+    ''' <param name="Button3">显示的第三个按钮,默认为空。</param>
+    ''' <param name="IsWarn">是否为警告弹窗,若为 True,第一个按钮会显示为红色。</param>
+    ''' <param name="IsWaitExit">是否等待弹窗关闭。若为 True,将会返回从 1 开始的点击的按钮编号,否则返回 0。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function MyMsgbox(ByVal Caption As String, Optional ByVal Title As String = APPLICATION_FULL_NAME, Optional ByVal Button1 As String = "确定", Optional ByVal Button2 As String = "", Optional ByVal Button3 As String = "", Optional ByVal IsWarn As Boolean = False, Optional ByVal IsWaitExit As Boolean = True) As Integer
+        Dim NewMsgBoxConverter As New MyMsgboxConverter With {.Button1 = Button1, .Button2 = Button2, .Button3 = Button3, .Caption = Caption, .IsWarn = IsWarn, .Title = Title, .IsWaitExit = IsWaitExit}
+        Return MyMsgbox(NewMsgBoxConverter)
+    End Function
+    ''' <summary>
+    ''' 存储弹窗信息的转换器。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Class MyMsgboxConverter
+        Public Title As String
+        Public Caption As String = ""
+        Public Button1 As String = "确定"
+        Public Button2 As String = ""
+        Public Button3 As String = ""
+        Public IsWarn As Boolean = False
+        Public IsWaitExit As Boolean = True
+        ''' <summary>
+        ''' 弹窗是否已经关闭。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public IsExited As Boolean = False
+        ''' <summary>
+        ''' 点击的按钮编号,从 1 开始。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public ReturnCode As Integer = 0
+    End Class
+
+    'Public Function MyMsgbox(ByVal title As String, ByVal page As Grid, Optional ByVal caption As String = "", Optional ByVal btn1 As String = "确定", Optional ByVal btn2 As String = "", Optional ByVal btn3 As String = "", Optional ByVal wait As Boolean = False) As Object
+    '    Dim wind As New MetroMsgbox With {.Owner = frmMain, .Width = MAINFORM_WIDTH - 20, .Height = MAINFORM_HEIGHT - 20, .Left = (My.Computer.Screen.WorkingArea.Width - MAINFORM_WIDTH) / 2 + 10, .Top = (My.Computer.Screen.WorkingArea.Height - MAINFORM_HEIGHT) / 2 + 10}
+    '    wind.labTitle.Text = title
+    '    wind.panCaption.Child = page
+    '    wind.btn1.Text = btn1
+    '    wind.btn2.Text = btn2
+    '    wind.btn3.Text = btn3
+    '    wind.btn2.Visibility = If(btn2 = "", Visibility.Collapsed, Visibility.Visible)
+    '    wind.btn3.Visibility = If(btn3 = "", Visibility.Collapsed, Visibility.Visible)
+    '    msgboxCallback = 0
+    '    If wait Then
+    '        wind.Show()
+    '        Return wind
+    '    Else
+    '        wind.ShowDialog()
+    '        Return msgboxCallback
+    '    End If
+    'End Function
+
+    ''' <summary>设置 WebBrowser 静默,即是否不产生弹窗。</summary>
+    ''' <param name="webBrowser">需要改变的 WebBrowser。</param>
+    ''' <param name="IsSilent">是否开启静默模式。</param>
+    Public Sub SetWebBrowserSilent(ByVal WebBrowser As WebBrowser, ByVal IsSilent As Boolean)
+        Try
+
+            Dim fi As FieldInfo = WebBrowser.GetType.GetField("_axIWebBrowser2", BindingFlags.Instance Or BindingFlags.NonPublic)
+            If Not IsNothing(fi) Then
+                Dim browser = fi.GetValue(WebBrowser)
+                If Not IsNothing(browser) Then
+                    browser.GetType().InvokeMember("Silent", BindingFlags.SetProperty, Nothing, browser, {IsSilent})
+                End If
+            End If
+
+        Catch ex As Exception
+            ExShow(ex, "设置浏览器失败", ErrorLevel.AllUsers)
+        End Try
+    End Sub
+#End Region
+
+#Region "随机"
+
+    '打乱数组
+    Public Function RandomArray(ByVal array As ArrayList) As ArrayList
+        RandomArray = New ArrayList
+        Do While array.Count > 0
+            Dim i As Integer = RandomInteger(0, array.Count - 1)
+            RandomArray.Add(array(i))
+            array.RemoveAt(i)
+        Loop
+    End Function
+    '获取随机整数
+    Public Function RandomInteger(ByVal min As Integer, ByVal max As Integer) As Integer
+        Return Math.Round((max - min) * Rnd.NextDouble) + min
+    End Function
+
+#End Region
+
+#Region "Review 完成 | 音乐"
+
+    ''' <summary>
+    ''' 播放音乐。
+    ''' </summary>
+    ''' <param name="FilePath">音乐文件完整路径。</param>
+    ''' <remarks></remarks>
+    Public Sub PlayBgm(ByVal FilePath As String, ByVal IsStop As Boolean, Optional ByVal IsShowHint As Boolean = False)
+        Try
+            If IsStop Then
+                '停止
+                log("[System] 停止播放音乐:" & FilePath)
+                mciSendString("close " & FilePath, 0, 0, 0)
+                PlaySound(Nothing, Nothing, Nothing)
+            Else
+                '播放
+                log("[System] 播放音乐:" & FilePath)
+                mciSendString("open " & FilePath, "", 0, 0)
+                Dim code = mciSendString("play " & FilePath & " repeat", "", 0, 0)
+                If code = 259 Then
+                    PlaySound(FilePath, 0, 131072 Or 1 Or 8) '换用 PlaySound 播放不支持循环的 .wav
+                    'If IsShowHint Then ShowHint("该音乐不支持循环播放,音乐将在播放完一次后停止。")
+                    'code = mciSendString("play " & FilePath, "", 0, 0)
+                ElseIf code = 277 Then
+                    If IsShowHint Then MyMsgbox("无效的音乐文件。" & vbCrLf & "PCL 不支持部分 MP3 的播放,请尝试更换文件。目前已知网易云下载的 MP3 会出现此问题,原因不明。", "播放音乐失败")
+                ElseIf Not code = 0 Then
+                    Throw New Exception(code)
+                End If
+            End If
+            'PlaySound(FilePath, 0, 131072 Or 1 Or 8)
+        Catch ex As Exception
+            ExShow(ex, "播放音乐失败", ErrorLevel.AllUsers)
+        End Try
+    End Sub
+
+#End Region
+
+End Module '基础函数
+
+'Module ComputerInfo
+'    '声明
+'    'Private cpuCounter As New PerformanceCounter("Processor", "% Processor Time", "_Total")
+'    'Private downloadCounter As New PerformanceCounter("Network Interface", "Bytes Received/sec", New PerformanceCounterCategory("Network Interface").GetInstanceNames()(0))
+'    'Private uploadCounter As New PerformanceCounter("Network Interface", "Bytes Sent/sec", New PerformanceCounterCategory("Network Interface").GetInstanceNames()(0))
+'    Private pcInfo As New Microsoft.VisualBasic.Devices.ComputerInfo
+'    '启动代码
+'    Public Sub InfoStartRun(ByVal time As Integer)
+'        Dim timer As New DispatcherTimer()
+'        timer.Interval = TimeSpan.FromMilliseconds(time)
+'        AddHandler timer.Tick, AddressOf InfoTimer
+'        timer.Start()
+'    End Sub
+'    '每次执行的代码
+'    Private Sub InfoTimer()
+'        '防止断网时获取错误
+'        On Error Resume Next
+'        infoDownloadSpeed = 0
+'        infoUploadSpeed = 0
+'        '获取值
+'        'infoCpuPercent = cpuCounter.NextValue
+'        'infoDownloadSpeed = downloadCounter.NextValue / 1024
+'        'infoUploadSpeed = uploadCounter.NextValue / 1024
+'    End Sub
+'    '获取信息
+'    Public infoCpuPercent As Integer = 0 'CPU使用率
+'    Public infoDownloadSpeed As Integer = 0 '下载速度(KB)
+'    Public infoUploadSpeed As Integer = 0 '上传速度(KB)
+'    Public Function infoNetworkSpeed() As Integer
+'        Return infoDownloadSpeed + infoUploadSpeed
+'    End Function '网络速度(KB)
+'    Public Function infoRamPercent() As Integer
+'        Return (pcInfo.TotalPhysicalMemory - pcInfo.AvailablePhysicalMemory) / pcInfo.TotalPhysicalMemory * 100
+'    End Function '内存使用率
+'    Public Function infoRamUsed() As Integer
+'        Return (pcInfo.TotalPhysicalMemory - pcInfo.AvailablePhysicalMemory) / 1024 / 1024
+'    End Function '内存使用量(MB)
+'    Public Function infoRamTotal() As Integer
+'        Return pcInfo.TotalPhysicalMemory / 1024 / 1024
+'    End Function '内存总量(MB)
+'    Public Function infoRamAvailable() As Integer
+'        Return pcInfo.AvailablePhysicalMemory / 1024 / 1024
+'    End Function '内存可用量(MB)
+'End Module '电脑信息获取
+
+Public Module MyAnimationer
+
+#Region "声明"
+    ''' <summary>
+    ''' 动画组列表。(组名→ArrayList)
+    ''' </summary>
+    ''' <remarks></remarks>
+    Private AniGroups As New Dictionary(Of String, ArrayList)
+    ''' <summary>
+    ''' 上一次记刻的时间。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public AniLastTick As Integer
+    ''' <summary>
+    ''' 动画执行是否开启。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public AniRunning As Boolean = False
+#End Region
+
+#Region "类与枚举"
+
+    ''' <summary>
+    ''' 单个动画对象。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Structure Animation
+
+        ''' <summary>
+        ''' 动画种类。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public AniType As AniType
+        ''' <summary>
+        ''' 动画副种类。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public AniSubType As AniSubType
+
+        ''' <summary>
+        ''' 动画总长度。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public TotalLength As Integer
+        ''' <summary>
+        ''' 已经执行的动画长度。如果为负数则为延迟。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public FinishLength As Integer
+        ''' <summary>
+        ''' 已经完成的百分比。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public Percent As Double
+        ''' <summary>
+        ''' 是否为“以后”。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public After As Boolean
+
+        ''' <summary>
+        ''' 插值器类型。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public Ease As AniEase
+        ''' <summary>
+        ''' 动画对象。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public Obj As Object
+        ''' <summary>
+        ''' 动画值。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public Value As Object
+        ''' <summary>
+        ''' 上次执行时的动画值。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public Last As Object
+
+        Public Overrides Function ToString() As String
+            Return GetStringFromEnum(AniType) & " | " & FinishLength & "/" & TotalLength & "(" & Math.Round(Percent * 100) & "%) | " & Obj.name & "(" & Obj.GetType.Name & ")"
+        End Function
+
+    End Structure
+    ''' <summary>
+    ''' 动画基础种类。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Enum AniType As Byte
+        ''' <summary>
+        ''' 单个Double的动画,包括位置、长宽、透明度等。这需要附属类型。
+        ''' </summary>
+        ''' <remarks></remarks>
+        DoubleAnimation = 0
+        ''' <summary>
+        ''' 颜色属性的动画。这需要附属类型。
+        ''' </summary>
+        ''' <remarks></remarks>
+        ColorAnimation = 1
+        ''' <summary>
+        ''' 缩放控件大小。比起4个DoubleAnimation来说效率更高。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Scale = 2
+        ''' <summary>
+        ''' 文字一个个出现。
+        ''' </summary>
+        ''' <remarks></remarks>
+        TextAppear = 3
+        ''' <summary>
+        ''' 执行代码。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Code = 4
+        ''' <summary>
+        ''' 执行按照规定格式书写的伪代码。比起Code来说这有很大的局限性,但效率更高。
+        ''' </summary>
+        ''' <remarks></remarks>
+        CodeEvent = 5
+        ''' <summary>
+        ''' 以 WPF 方式缩放控件。
+        ''' </summary>
+        ScaleTransform = 6
+        ''' <summary>
+        ''' 以 WPF 方式旋转控件。
+        ''' </summary>
+        RotateTransform = 7
+    End Enum
+    ''' <summary>
+    ''' 动画扩展种类。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Enum AniSubType As Byte
+
+        X = 0
+        Y = 1
+        Width = 2
+        Height = 3
+        Opacity = 4
+        Value = 5
+        Radius = 10
+        BorderThickness = 11
+
+        Background = 6
+        BorderBrush = 7
+        Foreground = 8
+        Stroke = 9
+
+    End Enum
+
+#End Region
+
+#Region "种类"
+
+    'DoubleAnimation
+
+    ''' <summary>
+    ''' 移动X轴的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。</param>
+    ''' <param name="Value">进行移动的值。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaX(ByVal Obj As Object, ByVal Value As Double, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.DoubleAnimation, .AniSubType = AniSubType.X,
+                                   .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay}
+    End Function
+    ''' <summary>
+    ''' 移动Y轴的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。</param>
+    ''' <param name="Value">进行移动的值。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaY(ByVal Obj As Object, ByVal Value As Double, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.DoubleAnimation, .AniSubType = AniSubType.Y,
+                                   .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay}
+    End Function
+    ''' <summary>
+    ''' 改变宽度的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。</param>
+    ''' <param name="Value">宽度改变的值。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaWidth(ByVal Obj As Object, ByVal Value As Double, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.DoubleAnimation, .AniSubType = AniSubType.Width,
+                                   .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay}
+    End Function
+    ''' <summary>
+    ''' 改变高度的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。</param>
+    ''' <param name="Value">高度改变的值。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaHeight(ByVal Obj As Object, ByVal Value As Double, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.DoubleAnimation, .AniSubType = AniSubType.Height,
+                                   .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay}
+    End Function
+    ''' <summary>
+    ''' 改变透明度的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。</param>
+    ''' <param name="Value">透明度改变的值。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaOpacity(ByVal Obj As Object, ByVal Value As Double, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.DoubleAnimation, .AniSubType = AniSubType.Opacity,
+                                   .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay}
+    End Function
+    ''' <summary>
+    ''' 改变对象的Value属性的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。</param>
+    ''' <param name="Value">Value属性改变的值。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaValue(ByVal Obj As Object, ByVal Value As Double, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.DoubleAnimation, .AniSubType = AniSubType.Value,
+                                   .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay}
+    End Function
+    ''' <summary>
+    ''' 改变对象的Radius属性的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。</param>
+    ''' <param name="Value">Radius属性改变的值。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaRadius(ByVal Obj As Object, ByVal Value As Double, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.DoubleAnimation, .AniSubType = AniSubType.Radius,
+                                   .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay}
+    End Function
+    ''' <summary>
+    ''' 改变对象的BorderThickness属性的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。</param>
+    ''' <param name="Value">BorderThickness属性改变的值。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaBorderThickness(ByVal Obj As Object, ByVal Value As Double, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.DoubleAnimation, .AniSubType = AniSubType.BorderThickness,
+                                   .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay}
+    End Function
+
+    'ColorAnimation
+
+    ''' <summary>
+    ''' 改变Background颜色属性的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。</param>
+    ''' <param name="Value">颜色改变的值。以RGB加减法进行计算。不用担心超额。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaBackGround(ByVal Obj As Object, ByVal Value As MyColor, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.ColorAnimation, .AniSubType = AniSubType.Background,
+                                   .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay, .Last = New MyColor}
+    End Function
+    ''' <summary>
+    ''' 改变BorderBrush颜色属性的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。</param>
+    ''' <param name="Value">颜色改变的值。以RGB加减法进行计算。不用担心超额。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaBorderBrush(ByVal Obj As Object, ByVal Value As MyColor, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.ColorAnimation, .AniSubType = AniSubType.BorderBrush,
+                                   .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay, .Last = New MyColor}
+    End Function
+    ''' <summary>
+    ''' 改变Foreground颜色属性的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。</param>
+    ''' <param name="Value">颜色改变的值。以RGB加减法进行计算。不用担心超额。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaForeGround(ByVal Obj As Object, ByVal Value As MyColor, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.ColorAnimation, .AniSubType = AniSubType.Foreground,
+                                   .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay, .Last = New MyColor}
+    End Function
+    ''' <summary>
+    ''' 改变Stroke颜色属性的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。</param>
+    ''' <param name="Value">颜色改变的值。以RGB加减法进行计算。不用担心超额。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaStroke(ByVal Obj As Object, ByVal Value As MyColor, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.ColorAnimation, .AniSubType = AniSubType.Stroke,
+                                   .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay, .Last = New MyColor}
+    End Function
+
+    'Scale
+
+    ''' <summary>
+    ''' 缩放控件的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。</param>
+    ''' <param name="Value">大小改变的百分比(如-0.6)或值。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <param name="Absolute">大小改变是否为绝对值。若为False则会按照控件当前大小计算相对缩放值。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaScale(ByVal Obj As Object, ByVal Value As Double, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False, Optional ByVal Absolute As Boolean = False) As Animation
+        Dim ChangeRect As New AdvancedRect
+        If Absolute Then
+            ChangeRect = New AdvancedRect(-0.5 * Value, -0.5 * Value, Value, Value)
+        Else
+            ChangeRect = New AdvancedRect(-0.5 * Obj.ActualWidth * Value, -0.5 * Obj.ActualHeight * Value, Obj.ActualWidth * Value, Obj.ActualHeight * Value)
+        End If
+        Return New Animation With {.AniType = AniType.Scale, .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = ChangeRect, .After = After, .FinishLength = -Delay}
+    End Function
+
+    'TextAppear
+
+    ''' <summary>
+    ''' 让一段文字一个个字出现或消失的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。必须是Label或TextBlock。</param>
+    ''' <param name="Hide">是否为一个个字隐藏。默认为False(一个个字出现)。这些字必须已经存在了。</param>
+    ''' <param name="TimePerText">是否采用根据文本长度决定时间的方式。</param>
+    ''' <param name="Time">动画长度(毫秒)。若TimePerText为True,这代表每个字所占据的时间。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaTextAppear(ByVal Obj As Object, Optional ByVal Hide As Boolean = False, Optional ByVal TimePerText As Boolean = True, Optional ByVal Time As Integer = 70, Optional ByVal Delay As Integer = 0, Optional ByVal After As Boolean = False) As Animation
+        'Are we cool yet?
+        Return New Animation With {.AniType = AniType.TextAppear, .TotalLength = If(TimePerText, Time * Len(If(Obj.GetType.Name = "TextBlock", Obj.Text, Obj.Context.ToString)), Time), .Obj = Obj, .Value = {If(Obj.GetType.Name = "TextBlock", Obj.Text, Obj.Context.ToString), Hide}, .After = After, .FinishLength = -Delay}
+    End Function
+
+    'Code
+
+    ''' <summary>
+    ''' 执行代码。
+    ''' </summary>
+    ''' <param name="Code">一个ThreadStart。这将会在执行时在主线程调用。</param>
+    ''' <param name="Delay">代码延迟执行的时间(毫秒)。</param>
+    ''' <param name="After">是否等到以前的动画完成后才执行。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaCode(ByVal Code As ThreadStart, Optional ByVal Delay As Integer = 0, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.Code,
+                                   .TotalLength = 1, .Value = Code, .After = After, .FinishLength = -Delay}
+    End Function
+
+    'CodeEvent
+
+    ''' <summary>
+    ''' 执行按照规定格式书写的伪代码。这有很大的局限性,但不会建立线程,性能也更好。
+    ''' </summary>
+    ''' <param name="Code">伪代码元素数组。</param>
+    ''' <param name="Delay">代码延迟执行的时间(毫秒)。</param>
+    ''' <param name="After">是否等到以前的动画完成后才执行。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaCode(ByVal Code As Array, Optional ByVal Delay As Integer = 0, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.CodeEvent,
+                                   .TotalLength = 1, .Value = Code, .After = After, .FinishLength = -Delay}
+    End Function
+
+    'ScaleTransform
+
+    ''' <summary>
+    ''' 按照 WPF 方式缩放控件的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。它必须已经拥有了单一的 ScaleTransform 值。</param>
+    ''' <param name="Value">大小改变的百分比(如-0.6)。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaScaleTransform(ByVal Obj As Object, ByVal Value As Double, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.ScaleTransform, .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay}
+    End Function
+
+    'RotateTransform
+
+    ''' <summary>
+    ''' 按照 WPF 方式旋转控件的动画。
+    ''' </summary>
+    ''' <param name="Obj">动画的对象。它必须已经拥有了单一的 ScaleTransform 值。</param>
+    ''' <param name="Value">大小改变的百分比(如-0.6)。</param>
+    ''' <param name="Time">动画长度(毫秒)。</param>
+    ''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
+    ''' <param name="Ease">插值器类型。</param>
+    ''' <param name="After">是否等到以前的动画完成后才继续本动画。</param>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Function AaRotateTransform(ByVal Obj As Object, ByVal Value As Double, Optional ByVal Time As Integer = 400, Optional ByVal Delay As Integer = 0, Optional ByVal Ease As AniEase = Nothing, Optional ByVal After As Boolean = False) As Animation
+        Return New Animation With {.AniType = AniType.RotateTransform, .TotalLength = Time, .Ease = If(Ease, New AniEaseNone), .Obj = Obj, .Value = Value, .After = After, .FinishLength = -Delay}
+    End Function
+
+    '特殊
+
+    ''' <summary>
+    ''' 将一个StackPanel中的各个项目依次显示。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Function AaStack(ByVal Stack As StackPanel) As ArrayList
+        AaStack = New ArrayList
+        Dim AniDelay As Integer = 0
+        For Each Item In Stack.Children
+            Item.Opacity = 0
+            AaStack.Add(AaOpacity(Item, 1, 100, AniDelay))
+            AniDelay = AniDelay + 25
+        Next
+    End Function
+
+#End Region
+
+#Region "插值器"
+
+    '基类
+    Public MustInherit Class AniEase
+        ''' <summary>
+        ''' 获取增加值。
+        ''' </summary>
+        ''' <param name="t1">较大的时间百分比。</param>
+        ''' <param name="t0">较小的时间百分比。</param>
+        ''' <returns></returns>
+        Public MustOverride Function GetDelta(t1 As Double, t0 As Double) As Double
+    End Class
+
+    ''' <summary>
+    ''' 线性,无缓动。
+    ''' </summary>
+    Public Class AniEaseNone
+        Inherits AniEase
+        Public Overrides Function GetDelta(t1 As Double, t0 As Double) As Double
+            t1 = MathRange(t1, 0, 1)
+            t0 = MathRange(t0, 0, 1)
+            Return t1 - t0
+        End Function
+    End Class
+
+    ''' <summary>
+    ''' 平滑开始。
+    ''' </summary>
+    Public Class AniEaseStart
+        Inherits AniEase
+        Public Overrides Function GetDelta(t1 As Double, t0 As Double) As Double
+            t1 = MathRange(t1, 0, 1)
+            t0 = MathRange(t0, 0, 1)
+            Return (t1 - t0) * (t1 + t0)
+        End Function
+    End Class
+
+    ''' <summary>
+    ''' 平滑结束。
+    ''' </summary>
+    Public Class AniEaseEnd
+        Inherits AniEase
+        Public Overrides Function GetDelta(t1 As Double, t0 As Double) As Double
+            t1 = MathRange(t1, 0, 1)
+            t0 = MathRange(t0, 0, 1)
+            Return (t1 - t0) * (2 - t1 - t0)
+        End Function
+    End Class
+
+    ''' <summary>
+    ''' 跳跃开始。
+    ''' </summary>
+    Public Class AniEaseJumpStart
+        Inherits AniEase
+
+        ''' <summary>
+        ''' 
+        ''' </summary>
+        ''' <param name="JumpEndPercent">跳跃结束时占整个动画的百分比。</param>
+        Public Sub New(JumpEndPercent As Double)
+            o = JumpEndPercent
+        End Sub
+        Private o As Double
+        Public Overrides Function GetDelta(t1 As Double, t0 As Double) As Double
+            t1 = MathRange(t1, 0, 1)
+            t0 = MathRange(t0, 0, 1)
+            Return (t1 - t0) * (t1 + t0 - o) / (1 - o)
+        End Function
+    End Class
+
+    ''' <summary>
+    ''' 跳跃结束。
+    ''' </summary>
+    Public Class AniEaseJumpEnd
+        Inherits AniEase
+
+        ''' <summary>
+        ''' 
+        ''' </summary>
+        ''' <param name="JumpStartPercent">跳跃开始时占整个动画的百分比。</param>
+        Public Sub New(JumpStartPercent As Double)
+            o = JumpStartPercent
+        End Sub
+        Private o As Double
+        Public Overrides Function GetDelta(t1 As Double, t0 As Double) As Double
+            t1 = MathRange(t1, 0, 1)
+            t0 = MathRange(t0, 0, 1)
+            Return (t1 - t0) * (o + 1 - t1 - t0) / o
+        End Function
+    End Class
+
+#End Region
+
+    '等待处理的动画组列表
+    Private AniWaitingList As New ArrayList
+    Private AniWaitingListName As New ArrayList
+
+    ''' <summary>
+    ''' 开始一个动画组。
+    ''' </summary>
+    ''' <param name="AniGroup">由Aa开头的函数初始化的Animation对象集合。</param>
+    ''' <param name="Name">动画组的名称。如果重复会直接停止同名动画组。</param>
+    ''' <param name="RefreshTime">是否重新开始这一帧的计时。如果该动画组连续执行请设置为False。</param>
+    ''' <remarks></remarks>
+    Public Sub AniStart(ByVal AniGroup As ArrayList, ByVal Name As String, Optional ByVal refreshTime As Boolean = True)
+
+        '把组动画(如 AniStack)分解
+        Dim AniGroupSet As New ArrayList
+        For i = 0 To AniGroup.Count - 1
+            If AniGroup(i).GetType.ToString.Contains("ArrayList") Then
+                AniGroupSet.AddRange(AniGroup(i))
+            Else
+                AniGroupSet.Add(AniGroup(i))
+            End If
+        Next i
+
+        '添加到正在执行的动画组
+        If refreshTime Then AniLastTick = Computer.Clock.TickCount '避免处理动画时已经造成了极大的延迟,导致动画突然结束
+        AniStop(Name)
+        AniGroups.Add(Name, AniGroupSet)
+
+    End Sub
+    ''' <summary>
+    ''' 开始一个动画组。
+    ''' </summary>
+    ''' <param name="AniGroup">由Aa开头的函数初始化的Animation对象集合。</param>
+    ''' <param name="Name">动画组的名称。如果重复会直接停止同名动画组。</param>
+    ''' <param name="RefreshTime">是否重新开始这一帧的计时。如果该动画组连续执行请设置为False。</param>
+    ''' <remarks></remarks>
+    Public Sub AniStart(ByVal aniGroup As Array, ByVal name As String, Optional ByVal refreshTime As Boolean = True)
+        '让Array和ArrayList都可以添加
+        AniStart(New ArrayList(aniGroup), name, refreshTime)
+    End Sub
+    ''' <summary>
+    ''' 停止一个动画组。
+    ''' </summary>
+    ''' <param name="name">需要停止的动画组的名称。</param>
+    ''' <remarks></remarks>
+    Public Sub AniStop(ByVal Name As String)
+        If AniGroups.ContainsKey(Name) Then AniGroups.Remove(Name)
+    End Sub
+
+    Private AniMissions As New ArrayList
+
+    Private AniFPSCounter As Integer = 0
+    Private AniFPSTimer As Long = 0
+    ''' <summary>
+    ''' 当前的动画 FPS。
+    ''' </summary>
+    Public AniFPS As Integer = 0
+
+    ''' <summary>
+    ''' 开始动画执行。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Sub AniStartRun()
+        '初始化计时器
+        AniLastTick = My.Computer.Clock.TickCount
+        AniFPSTimer = My.Computer.Clock.TickCount
+        AniRunning = True '标记动画执行开始
+
+        Dim th = New Thread(Sub()
+                                Do While True
+                                    '两帧之间的间隔时间
+                                    Dim DeltaTime As Integer = My.Computer.Clock.TickCount - AniLastTick
+                                    If DeltaTime < 2 Then GoTo Sleeper
+                                    AniLastTick = My.Computer.Clock.TickCount
+                                    '执行动画
+                                    frmMain.Dispatcher.Invoke(Sub() AniTimer(DeltaTime))
+                                    '记录 FPS
+                                    If AniLastTick - AniFPSTimer >= 1000 Then
+                                        AniFPS = AniFPSCounter
+                                        AniFPSCounter = 0
+                                        AniFPSTimer = AniLastTick
+                                        If MODE_DEVELOPER Then frmMain.Dispatcher.Invoke(Sub() frmMain.Title = "PCL / FPS : " & AniFPS)
+                                    End If
+                                    AniFPSCounter = AniFPSCounter + 1
+Sleeper:
+                                    '控制 FPS
+                                    Thread.Sleep(1)
+                                Loop
+                            End Sub)
+        th.Start()
+    End Sub
+
+    Private AniCodeList As New ArrayList
+    ''' <summary>
+    ''' 动画定时器事件。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Sub AniTimer(DeltaTime As Integer)
+        Rnd.NextDouble() '重置随机数发生器
+
+        For Each Code In AniCodeList
+            frmMain.Dispatcher.Invoke(Code)
+        Next
+        AniCodeList.Clear()
+
+        Try
+
+            Dim i As Integer = 0
+            '循环每个动画组
+            Do While i < AniGroups.Count
+                '初始化
+                Dim aniGroup = AniGroups.Values(i)
+                Dim CanRemoveAfter = True '是否应该去除“之后”标记
+                Dim ii = 0
+
+                '循环每个动画
+                Do While ii < aniGroup.Count
+                    Dim Anim As Animation = aniGroup(ii)
+                    '执行种类
+                    If Anim.After = False Then '之前
+                        CanRemoveAfter = False '取消“之后”标记 
+                        '增加执行时间
+                        Anim.FinishLength = Anim.FinishLength + DeltaTime
+                        '执行动画
+                        If Anim.FinishLength > 0 Then Anim = AniRun(Anim) '执行动画
+                        '如果当前动画已执行完毕
+                        If Anim.FinishLength >= Anim.TotalLength Then
+                            '删除
+                            aniGroup.RemoveAt(ii)
+                            GoTo NextAni
+                        End If
+                        aniGroup(ii) = Anim
+                    Else '之后
+                        If CanRemoveAfter Then
+                            '之后改为之前
+                            CanRemoveAfter = False
+                            Anim.After = False
+                            aniGroup(ii) = Anim
+                            '重新循环该动画
+                            GoTo NextAni
+                        Else
+                            '不能去除该“之后”标记,结束该动画组
+                            Exit Do
+                        End If
+                    End If
+                    ii = ii + 1
+NextAni:
+                Loop
+
+                '如果当前动画组都执行完毕则删除
+                If aniGroup.Count = 0 Then
+                    AniGroups.Remove(AniGroups.Keys(i))
+                    i = i - 1
+                End If
+                '继续循环
+                i = i + 1
+            Loop
+
+        Catch ex As Exception
+            ExShow(ex, "动画刻执行失败", ErrorLevel.Slient)
+        End Try
+    End Sub
+
+    ''' <summary>
+    ''' 执行一个动画。
+    ''' </summary>
+    ''' <param name="Ani">执行的动画对象。</param>
+    ''' <remarks></remarks>
+    Private Function AniRun(ByVal Ani As Animation) As Animation
+        Try
+            Select Case Ani.AniType
+
+                Case AniType.DoubleAnimation
+                    Dim Delta As Double = MathPercent(0, Ani.Value, Ani.Ease.GetDelta(Ani.FinishLength / Ani.TotalLength, Ani.Percent))
+                    Select Case Ani.AniSubType
+                        Case AniSubType.X
+                            SetLeft(Ani.Obj, GetLeft(Ani.Obj) + Delta)
+                        Case AniSubType.Y
+                            SetTop(Ani.Obj, GetTop(Ani.Obj) + Delta)
+                        Case AniSubType.Opacity
+                            Ani.Obj.Opacity = MathRange(Ani.Obj.Opacity + Delta, 0, 1)
+                        Case AniSubType.Width
+                            Ani.Obj.Width = MathRange(Ani.Obj.Width + Delta, 0)
+                        Case AniSubType.Height
+                            Ani.Obj.Height = MathRange(Ani.Obj.Height + Delta, 0)
+                        Case AniSubType.Value
+                            Ani.Obj.Value = Ani.Obj.Value + Delta
+                        Case AniSubType.Radius
+                            Ani.Obj.Radius = Ani.Obj.Radius + Delta
+                        Case AniSubType.BorderThickness
+                            Ani.Obj.BorderThickness = New Thickness(CType(Ani.Obj.BorderThickness, Thickness).Bottom + Delta)
+                    End Select
+
+                Case AniType.ColorAnimation
+                    '利用Last记录了余下的小数值
+                    Dim Delta As MyColor = MathPercent(New MyColor, Ani.Value, Ani.Ease.GetDelta(Ani.FinishLength / Ani.TotalLength, Ani.Percent)) + Ani.Last
+                    Select Case Ani.AniSubType
+                        Case AniSubType.Background
+                            Dim NewColor As MyColor = New MyColor(Ani.Obj.Background.Color) + Delta
+                            Ani.Obj.Background = NewColor
+                            Ani.Last = NewColor - New MyColor(Ani.Obj.Background.Color)
+                        Case AniSubType.Foreground
+                            Dim NewColor As MyColor = New MyColor(Ani.Obj.Foreground.Color) + Delta
+                            Ani.Obj.Foreground = NewColor
+                            Ani.Last = NewColor - New MyColor(Ani.Obj.Foreground.Color)
+                        Case AniSubType.Stroke
+                            Dim NewColor As MyColor = New MyColor(Ani.Obj.Stroke.Color) + Delta
+                            Ani.Obj.Stroke = NewColor
+                            Ani.Last = NewColor - New MyColor(Ani.Obj.Stroke.Color)
+                        Case AniSubType.BorderBrush
+                            Dim NewColor As MyColor = New MyColor(Ani.Obj.BorderBrush.Color) + Delta
+                            Ani.Obj.BorderBrush = NewColor
+                            Ani.Last = NewColor - New MyColor(Ani.Obj.BorderBrush.Color)
+                    End Select
+
+                Case AniType.Scale
+                    Dim Delta As Double = Ani.Ease.GetDelta(Ani.FinishLength / Ani.TotalLength, Ani.Percent)
+                    Ani.Obj.Margin = New Thickness(Ani.Obj.Margin.Left + MathPercent(0, Ani.Value.Left, Delta), Ani.Obj.Margin.Top + MathPercent(0, Ani.Value.Top, Delta), Ani.Obj.Margin.Right, Ani.Obj.Margin.Bottom)
+                    Ani.Obj.Width = Math.Max(Ani.Obj.Width + MathPercent(0, Ani.Value.Width, Delta), 0)
+                    Ani.Obj.Height = Math.Max(Ani.Obj.Height + MathPercent(0, Ani.Value.Height, Delta), 0)
+
+                Case AniType.TextAppear
+                    Dim NewText As String = Mid(Ani.Value(0), 1, If(Ani.Value(1), Len(Ani.Value(0)), 0) + Math.Round(Len(Ani.Value(0)) * If(Ani.Value(1), -1, 1) * Ani.Ease.GetDelta(Ani.FinishLength / Ani.TotalLength, 0)))
+                    If Ani.Obj.GetType.Name = "TextBlock" Then
+                        Ani.Obj.Text = NewText
+                    Else
+                        Ani.Obj.Context = NewText
+                    End If
+
+                Case AniType.Code
+                    AniCodeList.Add(Ani.Value)
+
+                Case AniType.CodeEvent
+                    Select Case Ani.Value(0)
+                        Case "Close"
+                            Ani.Value(1).Close()
+                        Case "Clear"
+                            Ani.Value(1).Clear()
+                        Case "Remove"
+                            Ani.Value(1).Remove(Ani.Value(2))
+                        Case "Add"
+                            Ani.Value(1).Add(Ani.Value(2))
+                        Case "Set"
+                            Ani.Value(1) = Ani.Value(2)
+                        Case "Tag"
+                            Ani.Value(1).Tag = Ani.Value(2)
+                        Case "Enabled"
+                            Ani.Value(1).IsEnabled = Ani.Value(2)
+                        Case "State"
+                            Ani.Value(1).State = Ani.Value(2)
+                        Case "IsHitTestVisible"
+                            Ani.Value(1).IsHitTestVisible = Ani.Value(2)
+                        Case "ShowInTaskbar"
+                            Ani.Value(1).ShowInTaskbar = Ani.Value(2)
+                        Case "Nothing"
+                            Ani.Value(1) = Nothing
+                        Case "Visible"
+                            Ani.Value(1).Visibility = If(Ani.Value(2), Visibility.Visible, Visibility.Collapsed)
+                        Case "End"
+                            EndForce()
+                        Case Else
+                            AaCodeCall(Ani.Value(0), Ani.Value)
+                    End Select
+
+                Case AniType.ScaleTransform
+                    Dim Delta As Double = MathPercent(0, Ani.Value, Ani.Ease.GetDelta(Ani.FinishLength / Ani.TotalLength, Ani.Percent))
+                    CType(Ani.Obj.RenderTransform, ScaleTransform).ScaleX = CType(Ani.Obj.RenderTransform, ScaleTransform).ScaleX + Delta
+                    CType(Ani.Obj.RenderTransform, ScaleTransform).ScaleY = CType(Ani.Obj.RenderTransform, ScaleTransform).ScaleY + Delta
+
+                Case AniType.RotateTransform
+                    Dim Delta As Double = MathPercent(0, Ani.Value, Ani.Ease.GetDelta(Ani.FinishLength / Ani.TotalLength, Ani.Percent))
+                    CType(Ani.Obj.RenderTransform, RotateTransform).Angle = CType(Ani.Obj.RenderTransform, RotateTransform).Angle + Delta
+
+            End Select
+            Ani.Percent = Ani.FinishLength / Ani.TotalLength '修改执行百分比
+        Catch ex As Exception
+            ExShow(ex, "执行动画失败:" & GetStringFromEnum(Ani.AniType), ErrorLevel.Slient)
+        End Try
+
+        Return Ani
+    End Function
+
+End Module '动画
+
+Public Module Web
+
+    ''' <summary>
+    ''' 正在下载的列表。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public WebGroups As New Dictionary(Of String, WebGroup)
+
+    Private WebDownloadingList As New ArrayList
+    Public WebDownloadCountMax As Integer = ReadIni("setup", "DownMaxinum", "20")
+
+#Region "类与枚举"
+
+    ''' <summary>
+    ''' 下载文件的文件大小处理方式。
+    ''' </summary>
+    Public Enum WebRequireSize As Integer
+        ''' <summary>
+        ''' 在下载前已经知道了文件大小。
+        ''' </summary>
+        Known = 0
+        ''' <summary>
+        ''' 下载前不知道文件大小,需要从网络中获取。
+        ''' </summary>
+        Require = 1
+        ''' <summary>
+        ''' 下载前不知道文件大小,也不获取大小。
+        ''' </summary>
+        DontNeed = 2
+        ''' <summary>
+        ''' 不需要知道准确大小,只要求文件至少有一定的大小。
+        ''' </summary>
+        AtLeast = 3
+    End Enum
+
+    Public Class WebGroup
+        Public Name As String
+        ''' <summary>
+        ''' 文件列表(WebFile)。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public Files As ArrayList
+        ''' <summary>
+        ''' 是否尝试从服务器确认文件大小。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public GetServerFileSize As WebRequireSize
+        ''' <summary>
+        ''' 下载成功时执行的线程。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public OnSuccess As System.Threading.ParameterizedThreadStart
+        ''' <summary>
+        ''' 下载失败时执行的线程。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public OnFail As System.Threading.ParameterizedThreadStart
+
+        ''' <summary>
+        ''' 已下载的文件数目(下载成功或失败的)。(用于文件数较多情况下的快速进度计算)
+        ''' </summary>
+        Public FinishedCount As Integer = 0
+        ''' <summary>
+        ''' 下载完成的文件数目的百分比。取值为0到1。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public ReadOnly Property Percent As Double
+            Get
+                Select Case Files.Count
+                    Case 0
+                        Percent = 0
+                    Case 1
+                        Percent = Math.Round(CType(Files(0), WebFile).Percent, 4)
+                    Case Is < 60
+                        Dim TotalPercent As Double = 0
+                        For i = 0 To Files.Count - 1
+                            TotalPercent = TotalPercent + CType(Files(i), WebFile).Percent
+                        Next
+                        Percent = Math.Round(TotalPercent / Files.Count, 4)
+                    Case Else
+                        Percent = Math.Round(FinishedCount / Files.Count, 4)
+                End Select
+            End Get
+        End Property
+        ''' <summary>
+        ''' 该文件组的唯一标识符。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public UUID As Integer = GetUUID()
+    End Class
+
+    Public Class WebFile
+
+        ''' <summary>
+        ''' 文件所有可能的网络URL。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public AllWebAddress As ArrayList
+        ''' <summary>
+        ''' 文件当前的网络URL。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public ReadOnly Property WebAddress As String
+            Get
+                Return If(AllWebAddress(0), "")
+            End Get
+        End Property
+
+        ''' <summary>
+        ''' 文件在本地的完整地址。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public ReadOnly Property LocalAddress As String
+            Get
+                Return LocalFolder & LocalName
+            End Get
+        End Property
+        ''' <summary>
+        ''' 文件在本地的所在的文件夹,以“\”结尾。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public LocalFolder As String
+
+        ''' <summary>
+        ''' 是否需要刷新文件名。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public RefreshName As Boolean = False
+        Private _LocalName As String = ""
+        ''' <summary>
+        ''' 当前的文件名。如果需要刷新文件名,它有可能改变。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public Property LocalName As String
+            Get
+                If _LocalName = "" Or IsNothing(_LocalName) Then _LocalName = GetFileNameFromPath(WebAddress)
+                Return _LocalName
+            End Get
+            Set(ByVal value As String)
+                _LocalName = value
+            End Set
+        End Property
+
+        ''' <summary>
+        ''' 使用下一个下载地址。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public Function NextAddress() As Boolean
+            If AllWebAddress.Count > 1 Then
+                AllWebAddress.RemoveAt(0)
+                If RefreshName Then LocalName = GetFileNameFromPath(WebAddress)
+                Return True
+            Else
+                Return False
+            End If
+        End Function
+
+        Private _State As WebDownloadStats = WebDownloadStats.FirstLoad
+        ''' <summary>
+        ''' 状态标记。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public Property State As WebDownloadStats
+            Get
+                Return _State
+            End Get
+            Set(ByVal value As WebDownloadStats)
+                If value = _State Then Exit Property
+
+                '改变状态
+                _State = value
+
+                Select Case value
+                    Case WebDownloadStats.FirstLoad
+                        '等待下载
+                        GetSize = 0
+                        Speed = 0
+                        Percent = 0
+                        SyncLock WebWaitingLockObj
+                            WebWaitingList.Add(Me)
+                        End SyncLock
+                    Case WebDownloadStats.Wait
+                        '等待下载
+                        frmDownloadRight.RefreshFile(Me)
+                        GetSize = 0
+                        Speed = 0
+                        Percent = 0
+                        SyncLock WebWaitingLockObj
+                            WebWaitingList.Insert(0, Me)
+                        End SyncLock
+                        WebWaitingToRun()
+                    Case WebDownloadStats.GetSize
+                        '获取大小
+                        frmDownloadRight.RefreshFile(Me)
+                        WebStateGetSize(Me)
+                    Case WebDownloadStats.Download
+                        '下载
+                        frmDownloadRight.RefreshFile(Me)
+                        WebStateDownload(Me)
+                    Case WebDownloadStats.Success
+                        '下载成功
+                        Speed = 0
+                        Percent = 1
+                        WebStateSuccess(Me)
+                        WebGroups(GroupName).FinishedCount = WebGroups(GroupName).FinishedCount + 1
+                        frmDownloadRight.RefreshFile(Me)
+                        WebFinishCheck(Me.GroupName)
+                    Case WebDownloadStats.Fail
+                        '下载失败
+                        GetSize = 0
+                        Speed = 0
+                        Percent = 0
+                        If Not WebStateFail(Me) Then
+                            WebGroups(GroupName).FinishedCount = WebGroups(GroupName).FinishedCount + 1
+                            frmDownloadRight.RefreshFile(Me)
+                            WebFinishCheck(Me.GroupName)
+                        End If
+                    Case WebDownloadStats.Retry
+                        '重试
+                        _State = value
+                        frmDownloadRight.RefreshFile(Me)
+                        Thread.Sleep(If(WebGroups(GroupName).Files.Count = 1, 200, 1000))
+#Disable Warning BC42026
+                        State = WebDownloadStats.Wait
+                End Select
+            End Set
+        End Property
+
+        ''' <summary>
+        ''' 该文件的唯一标识符。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public UUID As Integer = GetUUID()
+
+        Public GetServerFileSize As WebRequireSize
+        Public GroupName As String
+        Public RefreshThread As Thread
+
+        ''' <summary>
+        ''' 文件总大小,可能为 0。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public TotalSize As FileSize = 0
+        ''' <summary>
+        ''' 已经下载了的部分的大小。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public GetSize As FileSize = 0
+        ''' <summary>
+        ''' 下载速度,单位为秒。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public Speed As FileSize = 0
+        ''' <summary>
+        ''' 已经下载的百分比。取值为 0 到 1。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public Percent As Double = 0
+        ''' <summary>
+        ''' 计算下载速度的计刻。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public SpeedCount As Integer
+
+    End Class
+
+    Public Enum WebDownloadStats As Byte
+        ''' <summary>
+        ''' 第一次加入列表。等同于 Wait,但是不会刷新队列。
+        ''' </summary>
+        FirstLoad = 6
+        ''' <summary>
+        ''' 等待中。若队列有空闲,需要获取大小的转为 GetSize,不需要获取大小的转为 Download。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Wait = 0
+        ''' <summary>
+        ''' 正在获取大小。获取大小成功的转为 Download,失败的转为 Fail。
+        ''' </summary>
+        ''' <remarks></remarks>
+        GetSize = 1
+        ''' <summary>
+        ''' 正在下载。下载成功的转为 Success,失败的转为 Fail。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Download = 2
+        ''' <summary>
+        ''' 下载成功。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Success = 3
+        ''' <summary>
+        ''' 下载失败。可以重试的转为 Retry。
+        ''' </summary>
+        ''' <remarks></remarks>
+        Fail = 4
+        ''' <summary>
+        ''' 等待重试。半秒后转为 Wait。
+        ''' </summary>
+        Retry = 5
+    End Enum
+
+#End Region
+
+    '开始下载
+
+    ''' <summary>
+    ''' 需要下载的文件。
+    ''' </summary>
+    Public Class WebRequireFile
+        ''' <summary>
+        ''' 所有可能的下载地址。若前一个地址下载失败会自动尝试下一个地址。
+        ''' </summary>
+        Public WebURLs As ArrayList
+        ''' <summary>
+        ''' 本地文件夹地址。不包含文件名。
+        ''' </summary>
+        Public LocalFolder As String = ""
+        ''' <summary>
+        ''' 本地文件名,不包含文件夹路径。若为空,则会从下载地址自动生成文件名。
+        ''' </summary>
+        Public LocalName As String = ""
+        ''' <summary>
+        ''' 根据文件大小处理方式在这里提交准确大小或至少应有的大小。
+        ''' </summary>
+        Public KnownFileSize As Integer = 0
+    End Class
+    ''' <summary>
+    ''' 开始下载一个列表中的文件。
+    ''' </summary>
+    ''' <param name="SourceFiles">WebRequireFile 的一维数组。</param>
+    ''' <param name="Name">下载列表的名称。</param>
+    ''' <param name="OnSuccess">下载成功时执行的代码。</param>
+    ''' <param name="OnFail">下载失败时执行的代码。</param>
+    ''' <param name="RequireSize">文件大小处理方式。</param>
+    ''' <remarks></remarks>
+    Public Sub WebStart(ByVal SourceFiles() As WebRequireFile, ByVal Name As String, ByVal OnSuccess As ParameterizedThreadStart, ByVal OnFail As ParameterizedThreadStart, ByVal RequireSize As WebRequireSize)
+        Dim th As New Thread(Sub()
+
+                                 Try
+
+                                     '检查哪些文件需要下载,并将真正需要下载的文件整理为 CheckedFiles 数组
+
+                                     Dim CheckedFiles As New ArrayList(SourceFiles)
+
+                                     '检查文件是否存在文件名,如果不存在文件名则跳过过滤
+                                     If Not CheckedFiles(0).LocalName = "" Then
+
+                                         '过滤已经存在的文件
+                                         Select Case RequireSize
+                                             Case WebRequireSize.Known
+                                                 '已知文件大小,检查文件:如果大小一致则不下载
+
+                                                 For i = 0 To CheckedFiles.Count - 1
+                                                     If CheckedFiles.Count - 1 < i Then Exit For
+                                                     If GetFileSize(CheckedFiles(i).LocalFolder & CheckedFiles(i).LocalName) = CheckedFiles(i).KnownFileSize Then
+                                                         CheckedFiles.RemoveAt(i)
+                                                         i = i - 1
+                                                     End If
+                                                 Next
+
+                                             Case WebRequireSize.Require
+                                                 '不知道文件大小,所以不进行过滤
+
+                                             Case WebRequireSize.DontNeed
+                                                 '不需要知道文件大小,检查文件:如果文件存在则不下载
+
+                                                 For i = 0 To CheckedFiles.Count - 1
+                                                     If CheckedFiles.Count - 1 < i Then Exit For
+                                                     If File.Exists(CheckedFiles(i).LocalFolder & CheckedFiles(i).LocalName) Then
+                                                         CheckedFiles.RemoveAt(i)
+                                                         i = i - 1
+                                                     End If
+                                                 Next
+
+                                             Case WebRequireSize.AtLeast
+                                                 '文件有大小要求,检查文件:如果文件大小大于指定值则不下载
+
+                                                 For i = 0 To CheckedFiles.Count - 1
+                                                     If CheckedFiles.Count - 1 < i Then Exit For
+                                                     If GetFileSize(CheckedFiles(i).LocalFolder & CheckedFiles(i).LocalName) >= Math.Max(1, CheckedFiles(i).KnownFileSize) Then
+                                                         CheckedFiles.RemoveAt(i)
+                                                         i = i - 1
+                                                     End If
+                                                 Next
+
+                                         End Select
+
+                                         '去重
+                                         CheckedFiles = ArrayNoDouble(CheckedFiles.ToArray)
+
+                                         '如果没有剩余文件直接成功
+                                         If CheckedFiles.Count = 0 Then
+                                             '直接成功
+                                             frmMain.Dispatcher.Invoke(OnSuccess, Name)
+                                             Exit Sub
+                                         End If
+
+                                     End If
+
+                                     '初始化文件组
+                                     Dim NewGroup As New WebGroup With {.OnSuccess = OnSuccess, .OnFail = OnFail, .Files = New ArrayList, .GetServerFileSize = RequireSize, .Name = Name}
+
+                                     '二次处理文件,将 WebRequireFile 转为 WebFile
+
+                                     For i = 0 To CheckedFiles.Count - 1
+                                         Dim NewFile As WebFile
+                                         If CheckedFiles(0).LocalName = "" Then
+                                             NewFile = New WebFile With {.AllWebAddress = CheckedFiles(i).WebURLs, .GetServerFileSize = RequireSize, .GroupName = Name,
+                                                                                   .LocalFolder = CheckedFiles(i).LocalFolder, .TotalSize = New FileSize(CheckedFiles(i).KnownFileSize), .RefreshName = True}
+                                         Else
+                                             NewFile = New WebFile With {.AllWebAddress = CheckedFiles(i).WebURLs, .GetServerFileSize = RequireSize, .GroupName = Name,
+                                                                                   .LocalFolder = CheckedFiles(i).LocalFolder, .TotalSize = New FileSize(CheckedFiles(i).KnownFileSize), .LocalName = CheckedFiles(i).LocalName, .RefreshName = False}
+                                         End If
+                                         NewGroup.Files.Add(NewFile)
+                                     Next
+                                     WebWaitingList.AddRange(NewGroup.Files)
+
+                                     '添加进总列表
+                                     If NewGroup.Files.Count = 0 Then Exit Sub
+                                     If WebGroups.ContainsKey(Name) Then
+                                         frmDownloadRight.RemoveGroup(WebGroups(Name))
+                                         WebGroups.Remove(Name)
+                                     End If
+                                     log("[System] 开始下载列表文件:" & Name)
+                                     WebTaskbarShow = ReadIni("setup", "DownProcess", "True") = "True"
+                                     WebGroups.Add(Name, NewGroup)
+                                     frmDownloadRight.AddGroup(NewGroup)
+
+                                     '要求执行刷新
+                                     WebWaitingToRun()
+
+                                 Catch ex As Exception
+                                     ExShow(ex, "处理下载列表失败:" & Name, ErrorLevel.MsgboxAndFeedback)
+                                     frmMain.Dispatcher.Invoke(OnFail, Name)
+                                 End Try
+
+                             End Sub)
+        th.Start()
+    End Sub
+
+    '下载中
+
+    ''' <summary>
+    ''' 处于 Waiting 状态的文件。
+    ''' </summary>
+    Private WebWaitingList As New ArrayList
+    Public WebWaitingLockObj As Object = New Object
+    ''' <summary>
+    ''' 若队列有空闲,尝试让 Waiting 状态的文件开始下载。
+    ''' </summary>
+    Private Sub WebWaitingToRun()
+        SyncLock WebWaitingLockObj
+            For i = 0 To Math.Min(6, WebWaitingList.Count - 1)
+
+                Try
+                    If WebDownloadingList.Count >= WebDownloadCountMax Or i > Math.Min(6, WebWaitingList.Count - 1) Then Exit Sub
+                    If WebDownloadingList.Contains(WebWaitingList(i).UUID) Then GoTo NextFile
+
+                    WebDownloadingList.Add(WebWaitingList(i).UUID)
+                    WebWaitingList(i).State = If(WebWaitingList(i).GetServerFileSize = WebRequireSize.Require Or WebWaitingList(i).GetServerFileSize = WebRequireSize.AtLeast,
+                                WebDownloadStats.GetSize, WebDownloadStats.Download)
+                    WebWaitingList.RemoveAt(i) '移出等待队列
+                    i = i - 1
+                Catch
+                    i = i - 1
+                End Try
+
+NextFile:
+            Next
+        End SyncLock
+    End Sub
+
+    ''' <summary>
+    ''' 获取文件大小,之后转入 Fail 或 Download 状态。
+    ''' </summary>
+    Private Sub WebStateGetSize(File As WebFile)
+        Dim th As New Thread(Sub()
+
+                                 Try
+
+                                     If MODE_OFFLINE Then File.State = WebDownloadStats.Fail : Exit Sub
+                                     Select Case File.GetServerFileSize
+                                         Case WebRequireSize.AtLeast
+                                             '要求至少有一定大小
+                                             Dim TrueSize = GetFileSize(File.WebAddress, File)
+                                             If TrueSize < 1 Then
+                                                 Throw New Exception("获取文件大小失败!")
+                                             ElseIf TrueSize < File.TotalSize Then
+                                                 Throw New Exception("文件过小!(" & New FileSize(TrueSize).ToString & "/" & File.TotalSize.ToString & ")")
+                                             End If
+                                             File.TotalSize = TrueSize
+                                         Case WebRequireSize.Require
+                                             '只是需要知道大小
+                                             File.TotalSize = GetFileSize(File.WebAddress, File)
+                                             If File.TotalSize < 1 Then Throw New Exception("获取文件大小失败!")
+                                     End Select
+                                     '获取大小成功,开始下载
+                                     File.State = WebDownloadStats.Download
+
+                                 Catch ex As Exception
+                                     ExShow(ex, "获取文件大小失败(" & File.LocalName & ")", ErrorLevel.Slient)
+                                     File.State = WebDownloadStats.Fail
+                                 End Try
+
+                             End Sub)
+        th.Start()
+    End Sub
+
+    ''' <summary>
+    ''' 下载文件,之后转入 Fail 或 Success 状态。
+    ''' </summary>
+    Private Sub WebStateDownload(File As WebFile)
+        Dim th As New Thread(Sub()
+
+                                 Try
+                                     '准备
+                                     If Not Directory.Exists(File.LocalFolder) Then Directory.CreateDirectory(File.LocalFolder)
+                                     File.SpeedCount = My.Computer.Clock.TickCount
+                                     '下载
+                                     My.Computer.Network.DownloadFile(File.WebAddress, File.LocalAddress & DOWNLOADING_END, "", "", False, 10000, True)
+                                     '下载完毕检查
+                                     File.GetSize = GetFileSize(File.LocalAddress & DOWNLOADING_END)
+                                     If File.GetServerFileSize = WebRequireSize.DontNeed Or (File.GetSize = File.TotalSize And File.GetSize > 1) Then
+                                         '下载成功
+                                         IO.File.Delete(File.LocalAddress)
+                                         Rename(File.LocalAddress & DOWNLOADING_END, File.LocalAddress)
+                                         log("[Download] 下载文件成功:" & File.WebAddress)
+                                         File.State = WebDownloadStats.Success
+                                     Else
+                                         '下载失败
+                                         log("[Download] 下载文件失败(文件大小不匹配):" & File.LocalAddress & ",所需大小:" & File.TotalSize.ToString & ",实际大小:" & File.GetSize.ToString & "(" & File.GetSize.Value & ")", True)
+                                         IO.File.Delete(File.LocalAddress & DOWNLOADING_END)
+                                         File.State = WebDownloadStats.Fail
+                                     End If
+                                 Catch ex As Exception
+                                     ExShow(ex, "下载文件失败(" & File.LocalName & ")", ErrorLevel.Slient)
+                                     File.State = WebDownloadStats.Fail
+                                 End Try
+
+                             End Sub)
+        th.Start()
+
+        '刷新显示线程
+        File.RefreshThread = New Thread(Sub()
+                                            Try
+
+                                                Do While File.State = WebDownloadStats.Download
+                                                    Thread.Sleep(950)
+
+                                                    If Not File.State = WebDownloadStats.Download Then Exit Sub
+                                                    Dim NewSize = GetFileSize(File.LocalAddress & DOWNLOADING_END)
+                                                    File.Speed = (NewSize - File.GetSize) / (My.Computer.Clock.TickCount - File.SpeedCount) * 1000
+                                                    File.SpeedCount = My.Computer.Clock.TickCount
+                                                    File.GetSize = NewSize
+                                                    File.Percent = If(File.TotalSize = 0, 0, Math.Round(File.GetSize / File.TotalSize, 3))
+                                                    If Not File.State = WebDownloadStats.Download Then Exit Sub
+                                                    frmDownloadRight.RefreshFile(File)
+                                                Loop
+
+                                            Catch
+                                            End Try
+                                        End Sub)
+        File.RefreshThread.Start()
+    End Sub
+
+    ''' <summary>
+    ''' 下载失败,之后可能转入 Retry。若重试成功才返回 True。
+    ''' </summary>
+    ''' <param name="File"></param>
+    Private Function WebStateFail(File As WebFile)
+        '重试
+        If File.NextAddress() And Not MODE_OFFLINE Then
+            '重试成功
+            log("[Download] 重试地址:" & File.WebAddress)
+            File.State = WebDownloadStats.Retry
+            WebStateFail = True
+        Else
+            '重试失败
+            WebStateFail = False
+        End If
+        '队列处理
+        WebDownloadingList.Remove(File.UUID)
+        WebWaitingToRun()
+    End Function
+
+    ''' <summary>
+    ''' 下载成功。
+    ''' </summary>
+    Private Sub WebStateSuccess(File As WebFile)
+        '队列处理
+        WebDownloadingList.Remove(File.UUID)
+        WebWaitingToRun()
+    End Sub
+
+    '任务栏进度条
+    Public WebTaskbarShow As Boolean = True
+    Private WebTaskbarLastState As TaskbarItemProgressState = TaskbarItemProgressState.None
+    Private WebTaskbarLastValue As Double = 0
+
+    ''' <summary>
+    ''' 下载完成检查。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Sub WebFinishCheck(ByVal Name As String)
+        Try
+
+            If Not WebGroups.Keys.Contains(Name) Then Exit Sub
+            Dim Group As WebGroup = WebGroups(Name)
+            If IsNothing(Group.Files) Then Exit Sub
+            If Not Group.Files.Count = Group.FinishedCount Then Exit Sub
+            '遍历文件:如果存在失败的文件,将 isFail 设置为 True
+            Dim isFail As Boolean = False
+            For i = Group.Files.Count - 1 To 0 Step -1
+                If Group.Files(i).State = WebDownloadStats.Fail Then isFail = True
+            Next i
+            '下载完成
+            log("[Download] 列表文件下载完成:" & Name)
+            '结束
+            WebGroups.Remove(Name)
+            frmDownloadRight.RemoveGroup(Group)
+            If isFail Then
+                frmMain.Dispatcher.Invoke(Group.OnFail, Name)
+            Else
+                frmMain.Dispatcher.Invoke(Group.OnSuccess, Name)
+            End If
+
+        Catch ex As Exception
+            ExShow(ex, "检查下载列表出错")
+        End Try
+    End Sub
+
+    ''' <summary>
+    ''' 停止下载列表文件。
+    ''' </summary>
+    ''' <param name="Name"></param>
+    ''' <remarks></remarks>
+    Public Sub WebDownloadStop(ByVal Name As String)
+        If WebGroups.ContainsKey(Name) Then
+            frmDownloadRight.RemoveGroup(WebGroups(Name))
+            WebGroups.Remove(Name)
+        End If
+    End Sub
+
+    Public Sub WebStartRun()
+        Dim th As New Thread(Sub()
+                                 Do While True
+                                     WebTimer()
+                                     Thread.Sleep(250)
+                                 Loop
+                             End Sub)
+        th.Priority = ThreadPriority.BelowNormal
+        th.Start()
+    End Sub
+    Public Sub WebTimer()
+
+        If Not WebTaskbarShow Then Exit Sub
+
+        '应有值
+        Dim ShouldState As TaskbarItemProgressState
+        Dim ShouldValue As Double
+
+        '获取应有值
+        Try
+            If WebGroups.Count > 0 Then
+                Dim TotalPercent As Double = 0
+                For Each Group As WebGroup In WebGroups.Values
+                    TotalPercent = TotalPercent + Group.Percent
+                Next
+                ShouldState = If(TotalPercent = 0, TaskbarItemProgressState.Indeterminate, TaskbarItemProgressState.Normal)
+                ShouldValue = TotalPercent / WebGroups.Count
+            Else
+                ShouldState = TaskbarItemProgressState.None
+                ShouldValue = 0
+            End If
+        Catch
+        End Try
+
+        '更新值
+        If Not WebTaskbarLastState = ShouldState Then
+            frmMain.Dispatcher.Invoke(Sub() frmMain.TaskbarItemInfo.ProgressState = ShouldState)
+            WebTaskbarLastState = ShouldState
+        End If
+        If Not WebTaskbarLastValue = ShouldValue Then
+            frmMain.Dispatcher.Invoke(Sub() frmMain.TaskbarItemInfo.ProgressValue = ShouldValue)
+            WebTaskbarLastValue = ShouldValue
+        End If
+
+    End Sub
+
+End Module 'HTTP下载
+
+Public Module BitDrawer
+
+    Public GifList As New ArrayList 'Gif列表
+
+    'Timer
+    Public Sub GifStartRun()
+        Dim time As New DispatcherTimer()
+        time.Interval = TimeSpan.FromMilliseconds(1)
+        AddHandler time.Tick, AddressOf GifTimer
+        time.Start()
+    End Sub
+    Public Sub GifTimer()
+        For i = 0 To GifList.Count - 1
+            Dim gif As MyBitmap = GifList(i)
+            '切换Gif的帧
+            If My.Computer.Clock.TickCount - gif.RoundLastTime >= gif.RoundTime Then
+                gif.RoundLastTime = My.Computer.Clock.TickCount
+                If gif.CurrentFlame = gif.MaxFlame - 1 Then
+                    If gif.PlayOnce Then
+                        '如果只播放一次那么拜拜
+                        GifList.Remove(gif)
+                    Else
+                        '如果不只播放一次那么继续
+                        gif.SetGifFrame(0)
+                    End If
+                Else
+                    gif.SetGifFrame(gif.CurrentFlame + 1)
+                End If
+            End If
+            '刷新绑定控件的显示
+            If Not IsNothing(gif.Control) Then gif.Control.Source = New MyBitmap(gif.Pic)
+        Next
+    End Sub
+
+    '类
+    Public Class MyBitmap
+
+        ''' <summary>
+        ''' 存储的图片
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public Pic As System.Drawing.Bitmap
+        ''' <summary>
+        ''' (Gif)绑定的控件
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public Control As System.Windows.Controls.Image = Nothing
+        ''' <summary>
+        ''' (Gif)每一帧的时间
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public RoundTime As Integer
+        ''' <summary>
+        ''' (Gif)上一次改变时的时间
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public RoundLastTime As Integer
+        ''' <summary>
+        ''' (Gif)是否只播放一次
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public PlayOnce As Boolean = False
+        ''' <summary>
+        ''' (Gif)当前帧,从0开始
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public CurrentFlame As Integer = 0
+        ''' <summary>
+        ''' (Gif)最大帧数,从1开始
+        ''' </summary>
+        ''' <remarks></remarks>
+        Public MaxFlame As Integer
+
+        '类型转换
+        '支持的类:Image   ImageSource   Bitmap   ImageBrush
+        Public Shared Widening Operator CType(ByVal Image As System.Drawing.Image) As MyBitmap
+            Return New MyBitmap(Image)
+        End Operator
+        Public Shared Widening Operator CType(ByVal Image As MyBitmap) As System.Drawing.Image
+            Return Image.Pic
+        End Operator
+        Public Shared Widening Operator CType(ByVal Image As ImageSource) As MyBitmap
+            Return New MyBitmap(Image)
+        End Operator
+        Public Shared Widening Operator CType(ByVal Image As MyBitmap) As ImageSource
+            Dim ptr As IntPtr = Image.Pic.GetHbitmap()
+            Dim re As ImageSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
+                ptr,
+                IntPtr.Zero,
+                Int32Rect.Empty,
+                BitmapSizeOptions.FromEmptyOptions())
+            DeleteObject(ptr)
+            Return re
+        End Operator
+        Public Shared Widening Operator CType(ByVal Image As System.Drawing.Bitmap) As MyBitmap
+            Return New MyBitmap(Image)
+        End Operator
+        Public Shared Widening Operator CType(ByVal Image As MyBitmap) As System.Drawing.Bitmap
+            Return Image.Pic
+        End Operator
+        Public Shared Widening Operator CType(ByVal Image As ImageBrush) As MyBitmap
+            Return New MyBitmap(Image)
+        End Operator
+        Public Shared Widening Operator CType(ByVal Image As MyBitmap) As ImageBrush
+            Return New ImageBrush(New MyBitmap(Image.Pic))
+        End Operator
+
+        '构造函数
+        Public Sub New()
+        End Sub
+        Public Sub New(ByVal FilePath As String)
+            Try
+                If FilePath.StartsWith(PATH_IMAGE) Then
+                    '使用缓存
+                    If BitmapCache.ContainsKey(FilePath) Then
+                        Pic = BitmapCache(FilePath).Pic
+                    Else
+                        Pic = New MyBitmap(CType((New ImageSourceConverter).ConvertFromString(FilePath), ImageSource))
+                        BitmapCache.Add(FilePath, Pic)
+                    End If
+                Else
+                    Using inputStream As New FileStream(FilePath, FileMode.Open)
+                        Pic = New System.Drawing.Bitmap(inputStream)
+                    End Using
+                End If
+            Catch ex As Exception
+                Pic = My.Application.TryFindResource(FilePath)
+                If IsNothing(Pic) Then
+                    Pic = New System.Drawing.Bitmap(1, 1)
+                    ExShow(ex, "加载图片失败,该图片加载已被跳过:" & FilePath, ErrorLevel.DebugOnly)
+                End If
+            End Try
+        End Sub
+        Public Sub New(ByVal Image As ImageSource)
+            Using MS = New MemoryStream()
+                Dim Encoder = New PngBitmapEncoder()
+                Encoder.Frames.Add(BitmapFrame.Create(Image))
+                Encoder.Save(MS)
+                Pic = New System.Drawing.Bitmap(MS)
+            End Using
+        End Sub
+        Public Sub New(ByVal Image As System.Drawing.Image)
+            Pic = Image
+        End Sub
+        Public Sub New(ByVal Image As System.Drawing.Bitmap)
+            Pic = Image
+        End Sub
+        Public Sub New(ByVal Image As ImageBrush)
+            Using MS = New MemoryStream()
+                Dim Encoder = New BmpBitmapEncoder()
+                Encoder.Frames.Add(BitmapFrame.Create(Image.ImageSource))
+                Encoder.Save(MS)
+                Pic = New System.Drawing.Bitmap(MS)
+            End Using
+        End Sub
+
+        ''' <summary>
+        ''' 设置Gif的当前帧
+        ''' </summary>
+        ''' <param name="count">帧的编号,从0开始,超限制报错</param>
+        ''' <remarks></remarks>
+        Public Sub SetGifFrame(ByVal count As Integer)
+            Pic.SelectActiveFrame(New FrameDimension(Pic.FrameDimensionsList()(0)), count)
+            CurrentFlame = count
+        End Sub
+
+        ''' <summary>
+        ''' 把这个对象视作Gif初始化
+        ''' </summary>
+        ''' <param name="round">两帧之间的间隔(毫秒)。</param>
+        ''' <param name="once">是否只播放一次,True为是。</param>
+        ''' <param name="control">绑定的控件。</param>
+        ''' <remarks></remarks>
+        Public Sub GifLoad(ByVal round As Integer, ByVal once As Boolean, Optional ByVal control As System.Windows.Controls.Image = Nothing)
+            RoundTime = round
+            RoundLastTime = My.Computer.Clock.TickCount
+            PlayOnce = once
+            Me.Control = control
+            GifList.Add(Me)
+            MaxFlame = Pic.GetFrameCount(New FrameDimension(Pic.FrameDimensionsList()(0)))
+        End Sub
+
+        ''' <summary>
+        ''' 获取旋转的图片,这个方法不会导致原对象改变
+        ''' </summary>
+        ''' <param name="angle">旋转角度,单位为角度不是弧度啊</param>
+        ''' <returns>旋转后的Bitmap</returns>
+        ''' <remarks></remarks>
+        Public Function Rotation(ByVal angle As Double) As System.Drawing.Bitmap
+            With Me
+                Dim img As System.Drawing.Image = Me.Pic
+                Dim bitSize As Single = Math.Sqrt(img.Width ^ 2 + img.Height ^ 2)
+                bitSize = img.Width
+                Dim bmp As System.Drawing.Bitmap = New System.Drawing.Bitmap(CInt(bitSize), CInt(bitSize))
+                Using g As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(bmp)
+                    g.TranslateTransform(bitSize / 2, bitSize / 2)
+                    g.RotateTransform(angle)
+                    g.TranslateTransform(-bitSize / 2, -bitSize / 2)
+                    g.DrawImage(img, New System.Drawing.Rectangle(0, 0, img.Width, img.Width))
+                End Using
+                Return bmp
+            End With
+        End Function
+
+        ''' <summary>
+        ''' 获取左右翻转的图片,这个方法不会导致原对象改变
+        ''' </summary>
+        ''' <returns></returns>
+        ''' <remarks></remarks>
+        Public Function LeftRightFilp() As System.Drawing.Bitmap
+            Dim bmp As System.Drawing.Bitmap = New System.Drawing.Bitmap(Me.Pic)
+            bmp.RotateFlip(System.Drawing.RotateFlipType.RotateNoneFlipX)
+            Return bmp
+        End Function
+
+    End Class
+
+    '合并Bitmap
+    Public Function BitmapAdd(ByVal background As System.Drawing.Bitmap, ByVal position As System.Drawing.Point, ByVal img As System.Drawing.Bitmap) As System.Drawing.Bitmap
+        Dim graph As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(background)
+        graph.DrawImage(img, position)
+        Return background
+    End Function
+
+    '裁剪Bitmap
+    Public Function BitmapCut(ByVal img As BitmapSource, ByVal x As Integer, ByVal y As Integer, ByVal width As Integer, ByVal height As Integer) As BitmapSource
+        Return New CroppedBitmap(img, New Int32Rect(x, y, width, height))
+    End Function
+
+End Module '绘图
+
+'Public Module FTP
+
+'    Public Function DownloadFTPFile(ByVal ftpclient As FTPDownloader, ByVal webPath As String, ByVal localPath As String)
+'        File.Delete(localPath)
+'        DownloadFTPFile = False
+'        Try
+'            If Not ftpclient.ftpIsLogin Then ftpclient.Login()
+'            If webPath.Contains("/") Then ftpclient.ChangeDirectory(Mid(webPath, 1, webPath.LastIndexOf("/")))
+'            ftpclient.DownloadFile(Mid(webPath, webPath.LastIndexOf("/") + 2), localPath)
+'            Return True
+'        Catch ex As Exception
+'        End Try
+'    End Function '直接下载单个文件
+
+'    Public Class FTPDownloader
+
+'#Region "变量声明"
+'        Private m_sRemoteHost, m_sRemotePath, m_sRemoteUser As String
+'        Private m_sRemotePassword, m_sMess As String
+'        Private m_iRemotePort, m_iBytes As Int32
+'        Private m_objClientSocket As Socket
+'        Private m_iRetValue As Int32
+'        Public ftpIsLogin As Boolean
+'        Private m_sMes, m_sReply As String
+'        '设置用户来对FTP服务器读取和写入数据的数据包的大小
+'        Public Const BLOCK_SIZE = 512
+'        Private m_aBuffer(BLOCK_SIZE) As Byte
+'        Private ASCII As Encoding = Encoding.ASCII
+'        Public flag_bool As Boolean
+'        '普通变量定义
+'        Private m_sMessageString As String
+'#End Region
+
+'#Region "构造函数"
+'        Public Sub New()
+'            m_sRemoteHost = "localhost"
+'            m_sRemotePath = "."
+'            m_sRemoteUser = "anonymous"
+'            m_sRemotePassword = ""
+'            m_sMessageString = ""
+'            m_iRemotePort = 21
+'            ftpIsLogin = False
+'        End Sub
+'        Public Sub New(ByVal sRemoteHost As String, ByVal sRemotePath As String, ByVal sRemoteUser As String, ByVal sRemotePassword As String, ByVal iRemotePort As Int32)
+'            m_sRemoteHost = sRemoteHost
+'            m_sRemotePath = sRemotePath
+'            m_sRemoteUser = sRemoteUser
+'            m_sRemotePassword = sRemotePassword
+'            m_sMessageString = ""
+'            m_iRemotePort = iRemotePort
+'            ftpIsLogin = False
+'        End Sub
+'#End Region
+
+'#Region "属性"
+'        '设置或获取FTP服务器的名称
+'        Public Property RemoteHostFTPServer() As String
+'            Get
+'                Return m_sRemoteHost
+'            End Get
+'            Set(ByVal Value As String)
+'                m_sRemoteHost = Value
+'            End Set
+'        End Property
+'        '设置或获取FTP服务器的端口
+'        Public Property RemotePort() As Int32
+'            Get
+'                Return m_iRemotePort
+'            End Get
+'            Set(ByVal Value As Int32)
+'                m_iRemotePort = Value
+'            End Set
+'        End Property
+'        '设置或获取FTP服务器的远程路径
+'        Public Property RemotePath() As String
+'            Get
+'                Return m_sRemotePath
+'            End Get
+'            Set(ByVal Value As String)
+'                m_sRemotePath = Value
+'            End Set
+'        End Property
+'        '设置或获取FTP服务器的用户
+'        Public Property RemoteUser() As String
+'            Get
+'                Return m_sRemoteUser
+'            End Get
+'            Set(ByVal Value As String)
+'                m_sRemoteUser = Value
+'            End Set
+'        End Property
+'        '设置FTP服务器的密码
+'        Public Property RemotePassword() As String
+'            Get
+'                Return m_sRemotePassword
+'            End Get
+'            Set(ByVal Value As String)
+'                m_sRemotePassword = Value
+'            End Set
+'        End Property
+'        '设置信息
+'        Public Property MessageString() As String
+'            Get
+'                Return m_sMessageString
+'            End Get
+'            Set(ByVal Value As String)
+'                m_sMessageString = Value
+'            End Set
+'        End Property
+'#End Region
+
+'        '登录FTP服务器
+'        Public Function Login() As Boolean
+'            If ftpIsLogin Then Return True
+'            m_objClientSocket = _
+'             New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
+'            Dim ep As New IPEndPoint(Dns.GetHostEntry(m_sRemoteHost).AddressList(0), m_iRemotePort)
+'            Try
+'                m_objClientSocket.Connect(ep)
+'            Catch ex As Exception
+'                MessageString = m_sReply
+'                Throw New IOException("无法链接到FTP服务器。")
+'            End Try
+'            ReadReply()
+'            If (m_iRetValue <> 220) Then
+'                CloseConnection()
+'                MessageString = m_sReply
+'                Throw New IOException(m_sReply.Substring(4))
+'            End If
+'            '为了发送一个对服务器的用户登录ID,发送一个FTP命令
+'            SendCommand("USER " & m_sRemoteUser)
+'            If (Not (m_iRetValue = 331 Or m_iRetValue = 230)) Then
+'                Cleanup()
+'                MessageString = m_sReply
+'                Throw New IOException(m_sReply.Substring(4))
+'            End If
+'            If (m_iRetValue <> 230) Then
+'                '为了发送一个对服务器的用户密码,发送一个FTP命令
+'                SendCommand("PASS " & m_sRemotePassword)
+'                If (Not (m_iRetValue = 230 Or m_iRetValue = 202)) Then
+'                    Cleanup()
+'                    MessageString = m_sReply
+'                    Throw New IOException(m_sReply.Substring(4))
+'                End If
+'            End If
+'            ftpIsLogin = True
+'            '为了改变映射的远程服务器的文件夹的目录,调用用户定义的ChangeDirectory函数
+'            ChangeDirectory(m_sRemotePath)
+'            '返回最终结果
+'            Return ftpIsLogin
+'        End Function
+
+'        '获取文件列表
+'        Public Function GetFileList(ByVal sMask As String) As String()
+'            Dim cSocket As Socket
+'            Dim bytes As Int32
+'            Dim seperator As Char = ControlChars.Lf
+'            Dim mess() As String
+'            m_sMes = ""
+'            '检查是否登录
+'            If (Not (ftpIsLogin)) Then Login()
+'            cSocket = CreateDataSocket()
+'            '发送FTP命令
+'            SendCommand("NLST " & sMask)
+'            If (Not (m_iRetValue = 150 Or m_iRetValue = 125)) Then
+'                MessageString = m_sReply
+'                Throw New IOException(m_sReply.Substring(4))
+'            End If
+'            m_sMes = ""
+'            Do While (True)
+'                Array.Clear(m_aBuffer, 0, m_aBuffer.Length)
+'                bytes = cSocket.Receive(m_aBuffer, m_aBuffer.Length, 0)
+'                m_sMes += ASCII.GetString(m_aBuffer, 0, bytes)
+'                If (bytes < m_aBuffer.Length) Then
+'                    Exit Do
+'                End If
+'            Loop
+'            mess = m_sMes.Split(seperator)
+'            cSocket.Close()
+'            ReadReply()
+'            If (m_iRetValue <> 226) Then
+'                MessageString = m_sReply
+'                Throw New IOException(m_sReply.Substring(4))
+'            End If
+'            Return mess
+'        End Function
+'        '获取文件大小
+'        Public Function GetFileSize(ByVal sFileName As String) As Integer
+'            Dim size As Integer
+'            '检查是否登录
+'            If Not ftpIsLogin Then Login()
+'            '发送FTP命令
+'            SetBinaryMode(True)
+'            SendCommand("SIZE " & sFileName)
+'            size = 0
+'            If (m_iRetValue = 213) Then
+'                size = Int64.Parse(m_sReply.Substring(4))
+'            Else
+'                MessageString = m_sReply
+'                Throw New IOException(m_sReply.Substring(4))
+'            End If
+'            Return size
+'        End Function
+'        '下载文件
+'        Public Sub DownloadFile(ByVal fileName As String, Optional ByVal localFile As String = "", Optional ByVal isResume As Boolean = False)
+'            Dim st As Stream
+'            Dim cSocket As Socket
+'            Dim offset, npos As Integer
+'            If Not ftpIsLogin Then Login() '检查是否登录
+'            SetBinaryMode(True) '设置下载模式
+'            '检查下载目录
+'            If localFile = "" Then localFile = fileName
+'            If Not (File.Exists(localFile)) Then
+'                st = File.Create(localFile)
+'                st.Close()
+'            End If
+'            '下载
+'            Using output As New FileStream(localFile, FileMode.Open)
+'                cSocket = CreateDataSocket()
+'                offset = 0
+'                If isResume Then
+'                    offset = output.Length
+'                    If offset > 0 Then
+'                        '发送一个FTP命令重新启动
+'                        SendCommand("REST " & offset)
+'                        If (m_iRetValue <> 350) Then offset = 0
+'                        npos = output.Seek(offset, SeekOrigin.Begin)
+'                    End If
+'                End If
+'                '发送一个FTP命令重新找到一个文件
+'                SendCommand("RETR " & fileName)
+'                If (Not (m_iRetValue = 150 Or m_iRetValue = 125)) Then
+'                    MessageString = m_sReply
+'                    Throw New IOException(m_sReply.Substring(4))
+'                End If
+'                Do While (True)
+'                    Array.Clear(m_aBuffer, 0, m_aBuffer.Length)
+'                    m_iBytes = cSocket.Receive(m_aBuffer, m_aBuffer.Length, 0)
+'                    output.Write(m_aBuffer, 0, m_iBytes)
+'                    If (m_iBytes <= 0) Then
+'                        Exit Do
+'                    End If
+'                Loop
+'            End Using
+'            If (cSocket.Connected) Then
+'                cSocket.Close()
+'            End If
+'            ReadReply()
+'            If (Not (m_iRetValue = 226 Or m_iRetValue = 250)) Then
+'                MessageString = m_sReply
+'                Throw New IOException(m_sReply.Substring(4))
+'            End If
+'        End Sub
+
+'        '设置下载模式
+'        Public Sub SetBinaryMode(ByVal bMode As Boolean)
+'            '发送FTP命令,设置为二进制模式(True)或ASCII模式(False)
+'            SendCommand(If(bMode, "TYPE I", "TYPE A"))
+'            If (m_iRetValue <> 200) Then
+'                MessageString = m_sReply
+'                Throw New IOException(m_sReply.Substring(4))
+'            End If
+'        End Sub
+
+'        'Dim str_file_path As String = "C:\test.txt" '要上传的文件名及路径
+'        'Dim str_filename As String = IO.Path.GetFileName(str_file_path) '要上传的文件名
+'        'Dim MyFileSize As Integer = FileLen(str_file_path)   '要上传得文件大小
+'        'If (ftpClient.Login() = True) Then
+'        '    '创建一个新文件夹
+'        '    ftpClient.CreateDirectory("FTPFOLDERNEW")
+'        '    '将新的文件夹设置为活动文件夹。
+'        '    ftpClient.ChangeDirectory("FTPFOLDERNEW")
+'        '    '设置FTP模式
+'        '    ftpClient.SetBinaryMode(True)
+'        '    '从你的硬盘上上载一个文件到FTP网页
+'        '    ftpClient.UploadFile(str_file_path)
+'        '    '获得刚刚上传的文件的大小
+'        '    Dim lng_filesize As Integer
+'        '    lng_filesize = ftpClient.GetFileSize(str_filename)
+'        '    If lng_filesize = MyFileSize Then
+'        '        MsgBox("成功上传文件" & str_filename & " 文件大小" & lng_filesize)
+'        '    Else
+'        '        MsgBox("上传失败")
+'        '    End If
+'        '    '对上载文件重命名
+'        '    'ftpClient.RenameFile("SampleFile.xml", "SampleFile_new.xml")
+'        '    '删除一个文件
+'        '    'ftpClient.DeleteFile("SampleFile_new.xml")
+'        '    ftpClient.CloseConnection()
+'        'End If
+'        ''这是一个从你的本地硬盘上向你的FTP文件夹中上载文件的函数
+'        'Public Sub UploadFile(ByVal sFileName As String)
+'        '    UploadFile(sFileName, False)
+'        'End Sub
+'        ''这是一个从你的本地硬盘上向你的FTP网页上上载的函数和设置恢复标志
+'        'Public Sub UploadFile(ByVal sFileName As String,
+'        ' ByVal bResume As Boolean)
+'        '    Dim cSocket As Socket
+'        '    Dim offset As Integer
+'        '    Dim input As FileStream
+'        '    Dim bFileNotFound As Boolean
+'        '    If (Not (ftpIsLogin)) Then
+'        '        Login()
+'        '    End If
+'        '    cSocket = CreateDataSocket()
+'        '    offset = 0
+'        '    If (bResume) Then
+'        '        Try
+'        '            SetBinaryMode(True)
+'        '            offset = GetFileSize(sFileName)
+'        '        Catch ex As Exception
+'        '            offset = 0
+'        '        End Try
+'        '    End If
+'        '    If (offset > 0) Then
+'        '        SendCommand("REST " & offset)
+'        '        If (m_iRetValue <> 350) Then
+'        '            '远程服务器可能不支持恢复。
+'        '            offset = 0
+'        '        End If
+'        '    End If
+'        '    '发送一个FTP命令,存储一个文件。 
+'        '    SendCommand("STOR " & Path.GetFileName(sFileName))
+'        '    If (Not (m_iRetValue = 125 Or m_iRetValue = 150)) Then
+'        '        MessageString = m_sReply
+'        '        Throw New IOException(m_sReply.Substring(4))
+'        '    End If
+'        '    '在上载之前,检查文件是否存在。
+'        '    bFileNotFound = False
+'        '    If (File.Exists(sFileName)) Then
+'        '        '打开输入流读取源文件
+'        '        input = New FileStream(sFileName, FileMode.Open)
+'        '        If (offset <> 0) Then
+'        '            input.Seek(offset, SeekOrigin.Begin)
+'        '        End If
+'        '        '上载这个文件
+'        '        m_iBytes = input.Read(m_aBuffer, 0, m_aBuffer.Length)
+'        '        Do While (m_iBytes > 0)
+'        '            cSocket.Send(m_aBuffer, m_iBytes, 0)
+'        '            m_iBytes = input.Read(m_aBuffer, 0, m_aBuffer.Length)
+'        '        Loop
+'        '        input.Close()
+'        '    Else
+'        '        bFileNotFound = True
+'        '    End If
+'        '    If (cSocket.Connected) Then
+'        '        cSocket.Close()
+'        '    End If
+'        '    '如果找不到文件,检查返回值
+'        '    If (bFileNotFound) Then
+'        '        MessageString = m_sReply
+'        '        Throw New IOException("The file: " & sFileName & " was not found. " & _
+'        '       "Cannot upload the file to the FTP site")
+'        '    End If
+'        '    ReadReply()
+'        '    If (Not (m_iRetValue = 226 Or m_iRetValue = 250)) Then
+'        '        MessageString = m_sReply
+'        '        Throw New IOException(m_sReply.Substring(4))
+'        '    End If
+'        'End Sub
+'        ''从远程FTP服务器上删除一个文件。
+'        'Public Function DeleteFile(ByVal sFileName As String) As Boolean
+'        '    Dim bResult As Boolean
+'        '    bResult = True
+'        '    If (Not (ftpIsLogin)) Then
+'        '        Login()
+'        '    End If
+'        '    '发送一个FTP命令,删除一个文件。
+'        '    SendCommand("DELE " & sFileName)
+'        '    If (m_iRetValue <> 250) Then
+'        '        bResult = False
+'        '        MessageString = m_sReply
+'        '    End If
+'        '    '返回最终结果
+'        '    Return bResult
+'        'End Function
+'        ''在远程FTP服务器上重命名一个文件
+'        'Public Function RenameFile(ByVal sOldFileName As String,
+'        'ByVal sNewFileName As String) As Boolean
+'        '    Dim bResult As Boolean
+'        '    bResult = True
+'        '    If (Not (ftpIsLogin)) Then
+'        '        Login()
+'        '    End If
+'        '    '发送一个FTP命令,对一个文件重命名
+'        '    SendCommand("RNFR " & sOldFileName)
+'        '    If (m_iRetValue <> 350) Then
+'        '        MessageString = m_sReply
+'        '        Throw New IOException(m_sReply.Substring(4))
+'        '    End If
+'        '    '发送一个FTP命令,对一个文件更改为新名称
+'        '    '如果新的文件名存在,会被覆盖。
+'        '    SendCommand("RNTO " & sNewFileName)
+'        '    If (m_iRetValue <> 250) Then
+'        '        MessageString = m_sReply
+'        '        Throw New IOException(m_sReply.Substring(4))
+'        '    End If
+'        '    '返回最终结果
+'        '    Return bResult
+'        'End Function
+'        ''这是一个在远程服务器上创建目录的函数
+'        'Public Function CreateDirectory(ByVal sDirName As String) As Boolean
+'        '    Dim bResult As Boolean
+'        '    bResult = True
+'        '    If (Not (ftpIsLogin)) Then
+'        '        Login()
+'        '    End If
+'        '    '发送一个FTP命令,在FTP服务器上制作一个目录
+'        '    SendCommand("MKD " & sDirName)
+'        '    If (m_iRetValue <> 257) Then
+'        '        bResult = False
+'        '        MessageString = m_sReply
+'        '    End If
+'        '    '返回最终结果
+'        '    Return bResult
+'        'End Function
+'        ''这是一个在远程FTP服务器上删除目录的函数
+'        'Public Function RemoveDirectory(ByVal sDirName As String) As Boolean
+'        '    Dim bResult As Boolean
+'        '    bResult = True
+'        '    '检查是否已登录FTP服务器
+'        '    If (Not (ftpIsLogin)) Then
+'        '        Login()
+'        '    End If
+'        '    '发送一个FTP命令,删除在FTP服务器上的目录
+'        '    SendCommand("RMD " & sDirName)
+'        '    If (m_iRetValue <> 250) Then
+'        '        bResult = False
+'        '        MessageString = m_sReply
+'        '    End If
+'        '    '返回最终结果
+'        '    Return bResult
+'        'End Function
+
+'        '改变当前工作目录
+'        Public Function ChangeDirectory(ByVal sDirName As String) As Boolean
+'            Dim bResult As Boolean
+'            bResult = True
+'            '检查你是否在根目录
+'            If (sDirName.Equals(".")) Then Return False
+'            '检查是否已登录FTP服务器
+'            If Not (ftpIsLogin) Then Login()
+'            '发送FTP命令,改变在FTP服务器上的目录。
+'            SendCommand("CWD " & sDirName)
+'            If (m_iRetValue <> 250) Then
+'                bResult = False
+'                MessageString = m_sReply
+'            End If
+'            Me.m_sRemotePath = sDirName
+'            '返回最终结果
+'            Return bResult
+'        End Function
+'        '关闭FTP链接
+'        Public Sub CloseConnection()
+'            If Not (m_objClientSocket Is Nothing) Then
+'                '发送一个FTP命令,结束FTP服务系统
+'                SendCommand("QUIT")
+'            End If
+'            Cleanup()
+'        End Sub
+
+'#Region "内部函数"
+'        '从FTP服务器得到回应。
+'        Private Sub ReadReply()
+'            m_sMes = ""
+'            m_sReply = ReadLine()
+'            m_iRetValue = Int32.Parse(m_sReply.Substring(0, 3))
+'        End Sub
+'        '重置
+'        Private Sub Cleanup()
+'            If Not (m_objClientSocket Is Nothing) Then
+'                m_objClientSocket.Close()
+'                m_objClientSocket = Nothing
+'            End If
+'            ftpIsLogin = False
+'        End Sub
+'        '从FTP服务器读取一行
+'        Private Function ReadLine(Optional ByVal bClearMes As Boolean = False) As String
+'            Dim seperator As Char = ControlChars.Lf
+'            Dim mess() As String
+'            If (bClearMes) Then
+'                m_sMes = ""
+'            End If
+'            Do While (True)
+'                Array.Clear(m_aBuffer, 0, BLOCK_SIZE)
+'                Try
+'                    m_iBytes = m_objClientSocket.Receive(m_aBuffer, m_aBuffer.Length, 0)
+'                Catch ex As Exception
+'                    Login()
+'                    m_iBytes = m_objClientSocket.Receive(m_aBuffer, m_aBuffer.Length, 0)
+'                End Try
+'                m_sMes += ASCII.GetString(m_aBuffer, 0, m_iBytes)
+'                If (m_iBytes < m_aBuffer.Length) Then
+'                    Exit Do
+'                End If
+'            Loop
+'            mess = m_sMes.Split(seperator)
+'            If (m_sMes.Length > 2) Then
+'                m_sMes = mess(mess.Length - 2)
+'            Else
+'                m_sMes = mess(0)
+'            End If
+'            If (Not (m_sMes.Substring(3, 1).Equals(" "))) Then
+'                Return ReadLine(True)
+'            End If
+'            Return m_sMes
+'        End Function
+'        '向FTP服务器发送命令
+'        Private Sub SendCommand(ByVal command As String)
+'            command = command & ControlChars.CrLf
+'            Dim cmdbytes As Byte() = ASCII.GetBytes(command)
+'            m_objClientSocket.Send(cmdbytes, cmdbytes.Length, 0)
+'            ReadReply()
+'        End Sub
+'        '创建一个数据包 
+'        Private Function CreateDataSocket() As Socket
+'            Dim index1, index2, len As Int32
+'            Dim partCount, i, port As Int32
+'            Dim ipData, buf, ipAddress As String
+'            Dim parts(6) As Int32
+'            Dim ch As Char
+'            Dim s As Socket
+'            Dim ep As IPEndPoint
+'            '发送一个FTP命令,用于被动数据链接
+'            SendCommand("PASV")
+'            If (m_iRetValue <> 227) Then
+'                MessageString = m_sReply
+'                Throw New IOException(m_sReply.Substring(4))
+'            End If
+'            index1 = m_sReply.IndexOf("(")
+'            index2 = m_sReply.IndexOf(")")
+'            ipData = m_sReply.Substring(index1 + 1, index2 - index1 - 1)
+'            len = ipData.Length
+'            partCount = 0
+'            buf = ""
+'            For i = 0 To ((len - 1) And partCount <= 6)
+'                ch = Char.Parse(ipData.Substring(i, 1))
+'                If (Char.IsDigit(ch)) Then
+'                    buf += ch
+'                ElseIf (ch <> ",") Then
+'                    MessageString = m_sReply
+'                    Throw New IOException("Malformed PASV reply: " & m_sReply)
+'                End If
+'                If ((ch = ",") Or (i + 1 = len)) Then
+'                    Try
+'                        parts(partCount) = Int32.Parse(buf)
+'                        partCount += 1
+'                        buf = ""
+'                    Catch ex As Exception
+'                        MessageString = m_sReply
+'                        Throw New IOException("Malformed PASV reply: " & m_sReply)
+'                    End Try
+'                End If
+'            Next
+'            ipAddress = parts(0) & "." & parts(1) & "." & parts(2) & "." & parts(3)
+'            '在Visual Basic .Net 2002中进行调用。你想移动8位。在Visual Basic .NET 2002中,你必须将此数乘2的8次方。
+'            '端口=parts(4)*(2^8)
+'            '进行这个调用,并且用Visual Basic .NET 2003解释当前行。
+
+'            port = parts(4) << 8
+'            '确定数据端口数
+'            port = port + parts(5)
+'            s = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
+'            ep = New IPEndPoint(Dns.GetHostEntry(ipAddress).AddressList(0), port)
+'            Try
+'                s.Connect(ep)
+'            Catch ex As Exception
+'                MessageString = m_sReply
+'                Throw New IOException("Cannot connect to remote server.")
+'                '如果你不能链接到特定的FTP服务器,也就是说,将其布尔值设置为False。
+'                flag_bool = False
+'            End Try
+'            '如果你能够链接到特定的FTP服务器,将布尔值设置为True。
+'            flag_bool = True
+'            Return s
+'        End Function
+'#End Region
+
+'    End Class
+
+'End Module 'FTP协议下载 

+ 17 - 0
Plain Craft Launcher/Base/Radiobox.xaml

@@ -0,0 +1,17 @@
+<UserControl x:Class="Radiobox"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             mc:Ignorable="d" 
+             d:DesignHeight="33" d:DesignWidth="103" MinWidth="18" Background="#00000000" Foreground="#FF323232" FontSize="13">
+    <Grid HorizontalAlignment="Left" MinHeight="18" MaxHeight="18" MinWidth="20" Name="panMain" Opacity="0.8">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="18" />
+            <ColumnDefinition Width="Auto" />
+        </Grid.ColumnDefinitions>
+        <Label Name="label" Grid.Column="1" Padding="0" Margin="8,0,0,0" VerticalContentAlignment="Center" Height="18" VerticalAlignment="Top" BorderBrush="Black" Content="Radiobox" Foreground="{Binding RelativeSource={RelativeSource Self}, Path=Parent.Parent.Foreground}" FontFamily="{Binding RelativeSource={RelativeSource Self}, Path=Parent.Parent.FontFamily}" />
+        <Border BorderBrush="#FF333333" BorderThickness="2" CornerRadius="10" Height="18" HorizontalAlignment="Left" Name="border" VerticalAlignment="Top" Width="18"/>
+        <Ellipse Name="inner" Stroke="#FF333333" StrokeThickness="5" Height="10" Width="10" Opacity="0" HorizontalAlignment="Left" Margin="4,4,0,0" VerticalAlignment="Top" />
+    </Grid>
+</UserControl>

+ 146 - 0
Plain Craft Launcher/Base/Radiobox.xaml.vb

@@ -0,0 +1,146 @@
+Public Class Radiobox
+
+    Public Event Change(ByVal sender As Object, ByVal raiseByMouse As Boolean)
+
+    '自定义属性
+    Private _Checked As Boolean = False    '是否选中
+    Public Property Checked As Boolean
+        Get
+            Return _Checked
+        End Get
+        Set(ByVal value As Boolean)
+            SetChecked(value, False)
+        End Set
+    End Property
+    Public Sub SetChecked(ByVal value As Boolean, ByVal raiseByHand As Boolean)
+        If Not value = _Checked Then '只有变更时才执行
+            _Checked = value
+            If Me.IsLoaded Then '防止默认属性加载时导致错误
+                RaiseEvent Change(Me, raiseByHand)
+                '同一容器中保证只有1个单选框选中
+                Dim parentControl As Object = Me.Parent
+                If IsNothing(parentControl) Then Exit Sub
+                Dim checkedControl As ArrayList = New ArrayList
+                Dim checkedCount As Byte = 0
+                '统计单选框控件与选中个数
+                For Each con In parentControl.Children
+                    If con.GetType.Name = "Radiobox" Then
+                        checkedControl.Add(con)
+                        If con.Checked Then checkedCount = checkedCount + 1
+                    End If
+                Next
+                Select Case checkedCount
+                    Case 0 '没有选中项目
+                        checkedControl(0).Checked = True
+                    Case Is > 1 '选中项目多于1个
+                        If Me.Checked Then
+                            '如果本控件选中则取消其它所有控件的选中
+                            For Each con As Radiobox In checkedControl
+                                If con.Checked And Not con.Equals(Me) Then con.Checked = False
+                            Next
+                        Else
+                            '如果本控件未选中则只保留第一个选中的控件
+                            checkedCount = False '重复使用变量,实则统计是否为第一个选中控件
+                            For Each con As Radiobox In checkedControl
+                                If con.Checked Then
+                                    If checkedCount Then
+                                        con.Checked = False '修改Checked会自动触发Change事件,所以不用额外触发
+                                    Else
+                                        checkedCount = True
+                                    End If
+                                End If
+                            Next
+                        End If
+                End Select
+            End If
+
+            If Me.IsLoaded And UseControlAnimation Then '防止默认属性变更触发动画
+                If _Checked Then
+                    AniStop("Ra" & Me.Name & "CheckedChange")
+                    AniStart({
+                             AaScale(border, 4 / border.Width - 1, 140),
+                             AaOpacity(inner, 1 - inner.Opacity, 1, , , True),
+                             AaScale(inner, 10 - inner.Width, 140, , , , True),
+                             AaScale(border, 14 / border.Width, 140)
+                    }, "Ra" & Me.Name & "CheckedChange")
+                Else
+                    AniStop("Ra" & Me.Name & "CheckedChange")
+                    AniStart({
+                             AaScale(border, 4 / border.Width - 1, 140),
+                             AaScale(inner, 4 - inner.Width, 40, 100, , , True),
+                             AaOpacity(inner, -inner.Opacity, 1, , , True),
+                             AaScale(border, 14 / border.Width, 140)
+                    }, "Ra" & Me.Name & "CheckedChange")
+                End If
+            Else
+                inner.Opacity = If(_Checked, 1, 0)
+            End If
+        End If
+    End Sub
+    Public Property Context As String
+        Get
+            Return label.Content
+        End Get
+        Set(ByVal value As String)
+            label.Content = value
+        End Set
+    End Property '内容
+
+    '自定义事件
+    Public Sub RaiseChange()
+        RaiseEvent Change(Me, False)
+    End Sub '用于其它控件来引发本控件的Change事件
+
+    '默认事件
+    Private Sub Radiobox_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
+        SetChecked(True, True)
+    End Sub
+    Private Sub Radiobox_IsEnabledChanged(ByVal sender As Object, ByVal e As System.Windows.DependencyPropertyChangedEventArgs) Handles Me.IsEnabledChanged
+        If Me.IsLoaded And UseControlAnimation Then '防止默认属性变更触发动画
+            If Me.IsEnabled Then
+                AniStop("Ra" & Me.Name & "PointChange")
+                AniStart({
+                         AaBorderBrush(border, New MyColor(50, 50, 50) - border.BorderBrush, 150),
+                         AaStroke(inner, New MyColor(50, 50, 50) - inner.Stroke, 150),
+                         AaForeGround(label, New MyColor(50, 50, 50) - label.Foreground, 150)
+                }, "Ra" & Me.Name & "PointChange")
+            Else
+                AniStop("Ra" & Me.Name & "PointChange")
+                AniStart({
+                         AaBorderBrush(border, New MyColor(145, 145, 145) - border.BorderBrush, 150),
+                         AaStroke(inner, New MyColor(145, 145, 145) - inner.Stroke, 150),
+                         AaForeGround(label, New MyColor(145, 145, 145) - label.Foreground, 150)
+                }, "Ra" & Me.Name & "PointChange")
+            End If
+        Else
+            If Me.IsEnabled Then
+                inner.Stroke = New MyColor(50, 50, 50)
+                border.BorderBrush = New MyColor(50, 50, 50)
+                label.Foreground = New MyColor(50, 50, 50)
+            Else
+                inner.Stroke = New MyColor(145, 145, 145)
+                border.BorderBrush = New MyColor(145, 145, 145)
+                label.Foreground = New MyColor(145, 145, 145)
+            End If
+        End If
+    End Sub
+
+    '鼠标指向
+    Private Sub Radiobox_MouseEnter(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles Me.MouseEnter
+        If Me.Name = "" Then Me.Name = "Radio" & GetUUID()
+        AniStop("Ra" & Me.Name & "PointChange")
+        AniStart({
+                     AaOpacity(panMain, 1 - panMain.Opacity, 200)
+        }, "Ra" & Me.Name & "PointChange")
+    End Sub
+    Private Sub Radiobox_MouseLeave(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles Me.MouseLeave
+        'MouseLeave比IsEnabledChanged后执行,所以如果自定义事件修改了IsEnabled,将导致颜色显示错误,这个判断语句为了修复此bug
+        If Me.IsEnabled Then
+            AniStop("Ra" & Me.Name & "PointChange")
+            AniStart({
+                     AaOpacity(panMain, 0.8 - panMain.Opacity, 200)
+            }, "Ra" & Me.Name & "PointChange")
+        End If
+    End Sub
+
+End Class

+ 11 - 0
Plain Craft Launcher/Base/RightList.xaml

@@ -0,0 +1,11 @@
+<UserControl x:Class="RightList"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             mc:Ignorable="d" 
+             d:DesignHeight="303" d:DesignWidth="128">
+    <Grid>
+            
+    </Grid>
+</UserControl>

+ 3 - 0
Plain Craft Launcher/Base/RightList.xaml.vb

@@ -0,0 +1,3 @@
+Public Class RightList
+
+End Class

+ 9 - 0
Plain Craft Launcher/Base/RoundProcessbar.xaml

@@ -0,0 +1,9 @@
+<UserControl x:Class="RoundProcessbar"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             mc:Ignorable="d" 
+             d:DesignHeight="296" d:DesignWidth="140" MaxHeight="{Binding RelativeSource={RelativeSource Self}, Path=Width}" MinHeight="{Binding RelativeSource={RelativeSource Self}, Path=Width}" BorderBrush="#FF3896FF">
+    <Path Data="M50,0L50,20A50,50,0,0,1,80,50L100,50A50,50,0,0,0,50,0z" Fill="{Binding RelativeSource={RelativeSource Self}, Path=Parent.BorderBrush}" Name="Round" Height="{Binding RelativeSource={RelativeSource Self}, Path=Parent.Width}" Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.Width}" />
+</UserControl>

+ 117 - 0
Plain Craft Launcher/Base/RoundProcessbar.xaml.vb

@@ -0,0 +1,117 @@
+Public Class RoundProcessbar
+
+    '自定义属性
+    Private _Max As Integer = 100 '最大值
+    Public Property Max As Integer
+        Get
+            Return _Max
+        End Get
+        Set(ByVal value As Integer)
+            _Max = value
+            Refresh()
+        End Set
+    End Property
+    Private _Value As Double = 66 '当前值
+    Public Property Value As Double
+        Get
+            Return _Value
+        End Get
+        Set(ByVal value As Double)
+            Me._Value = value
+            Refresh()
+        End Set
+    End Property
+    Private _Radius As Integer = 10 '半径
+    Public Property Radius As Integer
+        Get
+            Return _Radius
+        End Get
+        Set(ByVal value As Integer)
+            _Radius = value
+            Refresh()
+        End Set
+    End Property
+    Private _ShowAtTaskbar As Boolean = False '是否绑定到任务栏
+    Public Property ShowAtTaskbar As Boolean
+        Get
+            Return _ShowAtTaskbar
+        End Get
+        Set(ByVal value As Boolean)
+            If value <> Me._ShowAtTaskbar Then
+                _ShowAtTaskbar = value
+                Refresh()
+            End If
+        End Set
+    End Property
+    Private _TaskbarState As Integer = TaskbarItemProgressState.Normal  '显示在任务栏的状态
+    Public Property TaskbarState As TaskbarItemProgressState
+        Get
+            Return _TaskbarState
+        End Get
+        Set(ByVal value As TaskbarItemProgressState)
+            If _ShowAtTaskbar Then
+                If Not value = _TaskbarState Then
+                    Me._TaskbarState = value
+                    Refresh()
+                End If
+            Else
+                Me._TaskbarState = value
+            End If
+        End Set
+    End Property
+    Private _OverValue As Boolean = False '是否允许超范围的Value
+    Public Property OverValue As Boolean
+        Get
+            Return _OverValue
+        End Get
+        Set(ByVal value As Boolean)
+            If value <> Me._OverValue Then
+                _OverValue = value
+                Refresh()
+            End If
+        End Set
+    End Property
+
+    '默认事件
+    Private Sub RoundProcessbar_SizeChanged(ByVal sender As Object, ByVal e As System.Windows.SizeChangedEventArgs) Handles Me.SizeChanged
+        Refresh()
+    End Sub
+
+    '自定义事件
+    Private Sub Refresh()
+        Me._Value = If(Me._OverValue, Me._Value, MathRange(Me._Value, 0, Me._Max))
+        Dim Angle As Double = 2 * Math.PI * MathRange(Me._Value / Me._Max, 0, 1) '计算饼状图角度
+        If Me._Value / Me._Max > 0.5 Then
+            Round.Data = New GeometryConverter().ConvertFrom( _
+                "M" & Me.Width / 2 & ",0" & _
+                "L" & Me.Width / 2 & "," & Me._Radius & _
+                "A" & Me.Width / 2 - Me._Radius & "," & Me.Width / 2 - Me._Radius & ",0,0,1," & Me.Width / 2 & "," & Me.Width - Me._Radius & _
+                "A" & Me.Width / 2 - Me._Radius & "," & Me.Width / 2 - Me._Radius & ",0,0,1," & Me.Width / 2 + (Me.Width / 2 - Me._Radius) * Math.Sin(Angle) & "," & Me.Width / 2 - (Me.Width / 2 - Me._Radius) * Math.Cos(Angle) & _
+                "L" & Me.Width / 2 * (1 + Math.Sin(Angle)) & "," & Me.Width / 2 * (1 - Math.Cos(Angle)) & _
+                "A" & Me.Width / 2 & "," & Me.Width / 2 & ",0,0,0," & Me.Width / 2 & "," & Me.Width & _
+                "A" & Me.Width / 2 & "," & Me.Width / 2 & ",0,0,0," & Me.Width / 2 & ",0" & _
+                "Z")
+        Else
+            Round.Data = New GeometryConverter().ConvertFrom( _
+                "M" & Me.Width / 2 & ",0" & _
+                "L" & Me.Width / 2 & "," & Me._Radius & _
+                "A" & Me.Width / 2 - Me._Radius & "," & Me.Width / 2 - Me._Radius & ",0,0,1," & Me.Width / 2 + (Me.Width / 2 - Me._Radius) * Math.Sin(Angle) & "," & Me.Width / 2 - (Me.Width / 2 - Me._Radius) * Math.Cos(Angle) & _
+                "L" & Me.Width / 2 * (1 + Math.Sin(Angle)) & "," & Me.Width / 2 * (1 - Math.Cos(Angle)) & _
+                "A" & Me.Width / 2 & "," & Me.Width / 2 & ",0,0,0," & Me.Width / 2 & ",0" & _
+                "Z")
+        End If
+
+        If _ShowAtTaskbar Then '如果在任务栏显示
+            frmMain.Dispatcher.Invoke(Sub()
+                                          If Me._Value / Me._Max < 0.0001 Then '没有进度时(兼容<0的Value)
+                                              frmMain.TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Indeterminate '设置为准备模式
+                                          Else
+                                              frmMain.TaskbarItemInfo.ProgressValue = MathRange(Me._Value, 0) / Me._Max '设置进度
+                                              frmMain.TaskbarItemInfo.ProgressState = Me._TaskbarState '设置模式
+                                          End If
+                                      End Sub)
+        End If
+
+    End Sub '重绘饼状图
+
+End Class

+ 12 - 0
Plain Craft Launcher/Base/Selecter.xaml

@@ -0,0 +1,12 @@
+<UserControl x:Class="Selecter"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             mc:Ignorable="d" 
+             d:DesignHeight="155" d:DesignWidth="46" Name="panBack" FontSize="14" Foreground="{DynamicResource ResourceKey=Color5}">
+    <Grid>
+        <Rectangle Height="24" HorizontalAlignment="Left" Name="lineSelect" UseLayoutRounding="True" Width="5" VerticalAlignment="Top" Stroke="{Binding ElementName=panBack, Path=Foreground}" StrokeThickness="3" Margin="0,4,0,0" />
+        <StackPanel Name="panLabs" Margin="14,0,0,0"></StackPanel>
+    </Grid>
+</UserControl>

+ 106 - 0
Plain Craft Launcher/Base/Selecter.xaml.vb

@@ -0,0 +1,106 @@
+Public Class Selecter
+
+    Public Event SelectionChange(ByVal Selection As String)
+    Public Event Click(ByVal Selection As String, ByVal Index As Integer)
+    Public NowSelect As New Label
+
+#Region "属性"
+    '线条宽度
+    Public Property LineWidth As Integer
+        Get
+            Return lineSelect.Width
+        End Get
+        Set(ByVal value As Integer)
+            lineSelect.Width = value
+        End Set
+    End Property
+    '显示的选项
+    Private _ShowList As Object() = {}
+    Public Property ShowList As Object()
+        Get
+            Return _ShowList
+        End Get
+        Set(ByVal value As Object())
+            If _ShowList.Equals(value) Then Exit Property
+            _ShowList = value
+
+            Try
+                panLabs.Children.Clear() '清空已有选项
+                Dim i = 0
+                For Each selection As String In value
+                    '每个选项
+                    Dim newLabel As New Label With {.Padding = New Thickness(0), .Name = "LabSelect" & GetUUID(), .Content = selection, .Height = 32, .HorizontalContentAlignment = HorizontalAlignment.Left, .VerticalContentAlignment = VerticalAlignment.Center, .Background = New MyColor(1, 200, 200, 200), .Opacity = 0.5, .FontSize = panBack.FontSize}
+                    newLabel.SetBinding(Control.ForegroundProperty, New Binding("Foreground") With {.ElementName = "panBack"})
+                    If HintList.Length > i Then newLabel.ToolTip = HintList(i)
+                    AddHandler newLabel.MouseEnter, AddressOf LabMouseEnter
+                    AddHandler newLabel.MouseLeave, AddressOf LabMouseLeave
+                    AddHandler newLabel.MouseUp, AddressOf LabMouseUp
+                    '添加Label
+                    panLabs.Children.Add(newLabel)
+                    i = i + 1
+                Next
+                NowSelect = panLabs.Children(0)
+                SelectIndex = 0
+                AniStop(Me.Name)
+                NowSelect.Opacity = 1
+                SetTop(lineSelect, 4)
+                panLabs.IsHitTestVisible = True
+            Catch ex As Exception
+                ExShow(ex, "设置选择条选项失败", ErrorLevel.MsgboxAndFeedback)
+            End Try
+        End Set
+    End Property
+    '选择选项
+    Public SelectIndexName As String = ""
+    Private _SelectIndex As Byte = 0
+    Public Property SelectIndex As Byte
+        Get
+            Return _SelectIndex
+        End Get
+        Set(ByVal value As Byte)
+            Try
+                value = MathRange(value, 0, panLabs.Children.Count - 1)
+                Dim sender As Label = panLabs.Children(value)
+                If sender.Content.Equals(SelectIndexName) Then Exit Property
+                SelectIndexName = sender.Content
+                If If(IsNothing(Me.Name), True, Me.Name = "selecter") Then Me.Name = "selecter" & GetUUID()
+                '动画
+                panLabs.IsHitTestVisible = False
+                Dim AnimationList As New ArrayList
+                For Each Lab As Label In panLabs.Children
+                    AniStart({AaOpacity(Lab, If(Lab.Equals(sender), 1, 0.5) - Lab.Opacity, 100)}, Lab.Name, False)
+                Next
+                AnimationList.Add(AaY(lineSelect, 4 + panLabs.Children.IndexOf(sender) * 32 - lineSelect.Margin.Top, , , New AniEaseJumpEnd(0.7)))
+                AnimationList.Add(AaCode({"IsHitTestVisible", panLabs, True}, , True))
+                AniStart(AnimationList, Me.Name)
+                '切换选项
+                NowSelect = sender
+                '引发事件
+                _SelectIndex = panLabs.Children.IndexOf(sender)
+                RaiseEvent SelectionChange(NowSelect.Content)
+            Catch ex As Exception
+                ExShow(ex, "切换选择条选项失败", ErrorLevel.MsgboxAndFeedback)
+            End Try
+        End Set
+    End Property
+    '点击可以改变
+    Public Property ClickToChange As Boolean = True
+    '各个选项的指向提示
+    Public Property HintList As String() = {}
+#End Region
+
+    Private Sub LabMouseEnter(ByVal sender As Label, ByVal e As System.Windows.Input.MouseEventArgs)
+        If Not NowSelect.Equals(sender) Then
+            AniStart({AaOpacity(sender, 0.8 - sender.Opacity, 70)}, sender.Name)
+        End If
+    End Sub
+    Private Sub LabMouseLeave(ByVal sender As Label, ByVal e As System.Windows.Input.MouseEventArgs)
+        If Not NowSelect.Equals(sender) Then
+            AniStart({AaOpacity(sender, 0.5 - sender.Opacity, 70)}, sender.Name)
+        End If
+    End Sub
+    Private Sub LabMouseUp(ByVal sender As Label, ByVal e As System.Windows.Input.MouseButtonEventArgs)
+        If ClickToChange And Not NowSelect.Equals(sender) Then SelectIndex = panLabs.Children.IndexOf(sender)
+        RaiseEvent Click(sender.Content, panLabs.Children.IndexOf(sender))
+    End Sub
+End Class

+ 16 - 0
Plain Craft Launcher/Base/Timer.xaml

@@ -0,0 +1,16 @@
+<UserControl x:Class="Timer"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             mc:Ignorable="d" 
+             d:DesignHeight="35" d:DesignWidth="47" Visibility="Collapsed" MinWidth="40" MinHeight="35" MaxHeight="35" BorderThickness="1" Foreground="Black" BorderBrush="#DC0A78FF" Background="#A055A4FF">
+    <Grid Height="35" Name="MainGrid" HorizontalAlignment="Center" VerticalAlignment="Center">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="20*" />
+            <RowDefinition Height="20*" />
+        </Grid.RowDefinitions>
+        <Label BorderThickness="1" Content="1000" DataContext="{Binding}" FontSize="10" FontWeight="Normal" Foreground="#C9FFFFFF" HorizontalAlignment="Center" HorizontalContentAlignment="Center" Name="TimeShow" Padding="0" Target="{Binding}" VerticalContentAlignment="Center" Grid.Row="1" VerticalAlignment="Center" Margin="5,0,5,2" />
+        <Label BorderThickness="1" Content="1" DataContext="{Binding}" FontSize="10" FontWeight="Normal" Foreground="#C9FFFFFF" HorizontalAlignment="Center" HorizontalContentAlignment="Center" Name="NameShow" Padding="0" Target="{Binding}" VerticalContentAlignment="Center" VerticalAlignment="Center" Margin="0,2,0,0" />
+    </Grid>
+</UserControl>

+ 72 - 0
Plain Craft Launcher/Base/Timer.xaml.vb

@@ -0,0 +1,72 @@
+Imports System.Windows.Threading
+
+Public Class Timer
+
+    '声明
+    Public Event Tick() '自定义事件
+
+    Public Timer As New DispatcherTimer() '时钟
+    Public TimerLoaded As Boolean = False '是否已经加载过了
+
+    '自定义属性
+    Private _Interval As Integer = 1000 '执行间隔
+    Public Property Interval As Integer
+        Get
+            Try
+                NameShow.Content = If(Me.Name.StartsWith("Timer") Or Me.Name.StartsWith("timer"), Me.Name.Remove(0, 5), Me.Name)
+            Catch ex As Exception
+                NameShow.Content = Me.Name
+            End Try
+            Return _Interval
+        End Get
+        Set(ByVal value As Integer)
+            _Interval = value
+            Timer.Interval = TimeSpan.FromMilliseconds(_Interval) '设置时钟Interval
+            TimeShow.Content = If(_Interval < 11, "MAX", Interval) '设置显示的Interval(由于每秒最多执行64次,故设置10及以下显示MAX)
+        End Set
+    End Property
+
+    '默认属性
+    Private Sub Timer_IsEnabledChanged(ByVal sender As Object, ByVal e As System.Windows.DependencyPropertyChangedEventArgs) Handles Me.IsEnabledChanged
+        Timer.IsEnabled = Me.IsEnabled '修改时钟Enabled
+        If Me.IsEnabled Then '修改显示颜色
+            Me.Background = New MyColor(Color.FromArgb(160, 85, 164, 255)) '蓝色
+            Me.BorderBrush = New MyColor(Color.FromArgb(220, 10, 120, 255))
+        Else
+            Me.Background = New MyColor(Color.FromArgb(160, 255, 20, 0)) '红色
+            Me.BorderBrush = New MyColor(Color.FromArgb(220, 255, 20, 0))
+        End If
+    End Sub
+
+    '自定义事件
+    ''' <summary>
+    ''' 重新开始本次计时。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Sub Reset()
+        Timer.Stop()
+        Timer.Start()
+        Timer.IsEnabled = Me.IsEnabled '修改时钟Enabled
+    End Sub
+
+    '默认事件
+    Private Sub Timer_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
+        If TimerLoaded Then Exit Sub
+
+        '初始化时钟
+        TimerLoaded = True
+        Timer.Interval = TimeSpan.FromMilliseconds(_Interval)
+        AddHandler Timer.Tick, Sub() RaiseEvent Tick()
+        Timer.Start()
+        Timer.IsEnabled = Me.IsEnabled '修改时钟Enabled
+
+        '修改Name显示
+        Try
+            NameShow.Content = If(Me.Name.StartsWith("Timer") Or Me.Name.StartsWith("timer"), Me.Name.Remove(0, 5), Me.Name)
+        Catch ex As Exception
+            NameShow.Content = Me.Name
+        End Try
+
+    End Sub
+
+End Class

+ 12 - 0
Plain Craft Launcher/Base/VScroll.xaml

@@ -0,0 +1,12 @@
+<UserControl x:Class="VScroll"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             mc:Ignorable="d" 
+             d:DesignHeight="82" d:DesignWidth="10" MinHeight="15" Background="#01000000" MinWidth="10" Name="panBack" Foreground="{DynamicResource ResourceKey=Color2}">
+    <Grid>
+        <Border BorderBrush="#FF808691" BorderThickness="5" CornerRadius="2" MaxWidth="10" MinHeight="15" MinWidth="10" Name="btnBack" Opacity="0.01" Width="10" />
+        <Border BorderBrush="{Binding ElementName=panBack, Path=Foreground}" BorderThickness="5" CornerRadius="2" MaxWidth="10" MinHeight="15" MinWidth="10" Name="btnDrag" Opacity="0.4" Width="10" Height="15" VerticalAlignment="Top" />
+    </Grid>
+</UserControl>

+ 295 - 0
Plain Craft Launcher/Base/VScroll.xaml.vb

@@ -0,0 +1,295 @@
+Public Class VScroll
+
+    Public Event Change(ByVal sender As Object)
+
+#Region "属性"
+
+    Private _ParentControl As Grid = Nothing
+    Private _ParentSubControl As Grid = Nothing
+    ''' <summary>
+    ''' 对应的控件的容器。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public ReadOnly Property ParentControl As Grid
+        Get
+            Return _ParentControl
+        End Get
+    End Property
+
+    Private _Control As Object = Nothing
+    ''' <summary>
+    ''' 对应的控件。它是需要被滚动的容器,并且被一个Grid包裹。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public ReadOnly Property Control As Object
+        Get
+            Return _Control
+        End Get
+    End Property
+
+    ''' <summary>
+    ''' 按钮离滚动条顶部的距离。
+    ''' </summary>
+    ''' <ValueMe></ValueMe>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Property ValueMe As Double
+        Get
+            Return btnDrag.Margin.Top
+        End Get
+        Set(ByVal ValueMe As Double)
+            If QuoteValue = 0 Then Exit Property
+            SetTop(btnDrag, MathRange(ValueMe, 0, MaxValueMe - QuoteValueMe))
+            SetTop(Control, -MathRange(
+                       If(QuoteValueMe = 15, (MaxValue - QuoteValue) / (MaxValueMe - QuoteValueMe) * ValueMe, MaxValue / QuoteValue * ValueMe),
+                   0, MaxValue - QuoteValue))
+        End Set
+    End Property
+
+    ''' <summary>
+    ''' 按钮高度。
+    ''' </summary>
+    ''' <ValueMe></ValueMe>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Property QuoteValueMe As Double
+        Get
+            Return btnDrag.ActualHeight
+        End Get
+        Set(ByVal ValueMe As Double)
+            btnDrag.Height = ValueMe
+        End Set
+    End Property
+
+    ''' <summary>
+    ''' 滚动条高度。
+    ''' </summary>
+    ''' <ValueMe></ValueMe>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public ReadOnly Property MaxValueMe As Double
+        Get
+            Return Me.ActualHeight
+        End Get
+    End Property
+
+    ''' <summary>
+    ''' 控件滚动的距离。
+    ''' </summary>
+    ''' <ValueMe></ValueMe>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Property Value As Double
+        Get
+            Return If(IsNothing(Control), 0, -Control.Margin.Top)
+        End Get
+        Set(ByVal ValueMe As Double)
+            Me.ValueMe = If(QuoteValueMe = 15, (MaxValueMe - QuoteValueMe) / (MaxValue - QuoteValue) * ValueMe, MaxValueMe / MaxValue * ValueMe)
+        End Set
+    End Property
+
+    ''' <summary>
+    ''' 控件可以显示的最大高度。等同于容器的高度。
+    ''' </summary>
+    ''' <ValueMe></ValueMe>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public ReadOnly Property QuoteValue As Double
+        Get
+            Return If(IsNothing(ParentControl), 0, ParentControl.ActualHeight)
+        End Get
+    End Property
+
+    ''' <summary>
+    ''' 最大值。等同于控件的高度。
+    ''' </summary>
+    ''' <ValueMe></ValueMe>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public ReadOnly Property MaxValue As Double
+        Get
+            Return If(IsNothing(Control), 0, Control.ActualHeight)
+        End Get
+    End Property
+
+    Private _InCenter As Boolean
+    ''' <summary>
+    ''' 是否在控件高度不足最高高度时把它居中。
+    ''' </summary>
+    ''' <ValueMe></ValueMe>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public ReadOnly Property InCenter As Boolean
+        Get
+            Return _InCenter
+        End Get
+    End Property
+
+    ''' <summary>
+    ''' 在判断滚动条是否显示时的容错范围。
+    ''' </summary>
+    ''' <value></value>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Public Property WrongRange As Double = 2
+
+#End Region
+
+#Region "鼠标动画"
+
+    Private Sub VScroll_MouseEnter(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles Me.MouseEnter
+        If Len(Me.Name) < 1 Then Me.Name = "Aniamtioner" & GetUUID() '确保这个控件有名称
+        AniStart({
+                 AaOpacity(btnDrag, 1 - btnDrag.Opacity, 200),
+                 AaOpacity(btnBack, 0.3 - btnBack.Opacity, 200)
+             }, Me.Name & "Mouse")
+    End Sub
+
+    Private Sub VScroll_MouseLeave(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles Me.MouseLeave
+        If IsNothing(DragingScroll) Then DragMouseLeave()
+    End Sub
+    ''' <summary>
+    ''' 触发离开动画。
+    ''' </summary>
+    ''' <remarks></remarks>
+    Public Sub DragMouseLeave()
+        AniStart({
+                 AaOpacity(btnDrag, 0.4 - btnDrag.Opacity, 200),
+                 AaOpacity(btnBack, 0.01 - btnBack.Opacity, 200)
+             }, Me.Name & "Mouse")
+    End Sub
+
+#End Region
+
+#Region "鼠标拖动"
+
+    Private ClickedY As Integer '鼠标指针在按钮上的相对Y坐标
+    Private ShouldTop As Integer '按钮到顶部的距离
+    Private Sub btnDrag_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles btnDrag.MouseDown
+        ShouldTop = btnDrag.Margin.Top
+        ClickedY = e.GetPosition(btnDrag).Y
+        DragingScroll = Me
+    End Sub
+    Public Sub Draging(ByVal Y As Integer)
+        ShouldTop = MathRange(Y - ClickedY + ShouldTop, 0, MaxValueMe - QuoteValueMe) '实际应该在的位置
+        If Not ValueMe = ShouldTop Then ValueMe = ShouldTop
+    End Sub
+
+#End Region
+
+    ''' <summary>
+    ''' 设置绑定的控件。这个方法务必只能执行一次。
+    ''' </summary>
+    ''' <param name="Control">需要被拖动的控件。它需要被一个Grid覆盖。</param>
+    ''' <param name="InCenter">是否在控件高度不足最高高度时把它居中。</param>
+    ''' <remarks></remarks>
+    Public Function SetControl(ByVal Control As StackPanel, ByVal InCenter As Boolean) As Boolean
+        '可靠性检查
+        If IsNothing(Control) Then Return False
+        If IsNothing(Control.Parent) Then Return False
+        If Not Control.Parent.GetType.Name = "Grid" Then Return False
+        If Not IsNothing(ParentControl) Then Return False
+
+        Try
+            '初始化
+            _ParentControl = Control.Parent
+            _ParentControl.UpdateLayout()
+            _ParentSubControl = New Grid With {.Width = _ParentControl.ActualWidth + 0.51, .VerticalAlignment = VerticalAlignment.Top}
+            _Control = Control
+            _InCenter = InCenter
+            SetTop(btnDrag, 0)
+            '初始化控件
+            _ParentControl.SetValue(ContentPresenter.ContentProperty, Nothing)
+            _ParentControl.Children.Clear()
+            _ParentControl.Children.Add(_ParentSubControl)
+            _ParentSubControl.Children.Add(_Control)
+            _Control.VerticalAlignment = VerticalAlignment.Top
+            '添加委托
+            AddHandler Me.SizeChanged, AddressOf Refresh
+            AddHandler Control.SizeChanged, AddressOf Refresh
+            AddHandler ParentControl.SizeChanged, AddressOf Refresh
+            AddHandler btnDrag.SizeChanged, AddressOf Refresh
+        Catch ex As Exception
+            ExShow(ex, "滚动条绑定失败", ErrorLevel.MsgboxAndFeedback)
+            Return False
+        End Try
+
+        '刷新滚动条
+        Refresh(Nothing, Nothing)
+        Return True
+    End Function
+
+    ''' <summary>
+    ''' 刷新QuoteValue与高度等。
+    ''' </summary>
+    ''' <returns></returns>
+    ''' <remarks></remarks>
+    Private Function Refresh(ByVal sender, ByVal e) As Boolean
+        Try
+
+            '可靠性检查
+            If IsNothing(_Control) Or IsNothing(ParentControl) Then Return False
+            If QuoteValue = 0 Or MaxValue = 0 Or Not MathCheck(QuoteValueMe) Then Return False
+
+            _ParentSubControl.Width = _ParentControl.ActualWidth + 0.51
+
+            Dim OldValueMe = ValueMe
+            Dim OldValue = Value
+
+            QuoteValueMe = MathRange(MaxValueMe / MaxValue * QuoteValue, 15, MaxValueMe)
+            If MaxValueMe - QuoteValueMe < WrongRange Then
+                '过短
+                AniStart({AaOpacity(Me, -Me.Opacity, 100)}, Me.Name & "Opacity", False)
+                _Control.Margin = New Thickness(0)
+                If InCenter Then _ParentSubControl.VerticalAlignment = VerticalAlignment.Center
+                Value = 0
+            Else
+                '正常
+                AniStart({AaOpacity(Me, 1 - Me.Opacity, 100)}, Me.Name & "Opacity", False)
+                _ParentSubControl.VerticalAlignment = VerticalAlignment.Top
+                Value = OldValue
+            End If
+
+            If Not ValueMe = OldValueMe Then RaiseEvent Change(Me)
+            Return True
+        Catch ex As Exception
+            ExShow(ex, "刷新滚动条失败", ErrorLevel.MsgboxAndFeedback)
+            Return False
+        End Try
+    End Function
+
+    ''' <summary>
+    ''' 委托处理鼠标滚轮事件。将试图接管滚轮的控件添加一个Handler到这个事件。
+    ''' </summary>
+    ''' <param name="sender"></param>
+    ''' <param name="e"></param>
+    ''' <remarks></remarks>
+    Public Sub RunMouseWheel(ByVal sender As Object, ByVal e As System.Windows.Input.MouseWheelEventArgs)
+        If Me.Visibility = Visibility.Visible Then
+            '如果时间为负数就不会执行,所以加绝对值
+            AniStart({
+                     AaValue(Me, -e.Delta, Math.Abs(e.Delta) * 2, , New AniEaseEnd)
+                 }, "Scroll" & GetUUID(), False)
+        End If
+    End Sub
+
+    ''' <summary>
+    ''' 点击滚动条空白部分改变值。
+    ''' </summary>
+    ''' <param name="sender"></param>
+    ''' <param name="e"></param>
+    ''' <remarks></remarks>
+    Private Sub btnBack_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles btnBack.MouseUp
+        If btnDrag.IsMouseOver Then Exit Sub '点在空白部分
+        If e.GetPosition(btnDrag).Y < 0 Then
+            AniStart({
+                     AaValue(Me, -QuoteValue, 200, , New AniEaseEnd)
+                 }, "ScrollClick" & GetUUID(), False)
+        Else
+            AniStart({
+                     AaValue(Me, QuoteValue, 200, , New AniEaseEnd)
+                 }, "ScrollClick" & GetUUID(), False)
+        End If
+    End Sub
+
+End Class

BIN
Plain Craft Launcher/Images/Back.png


BIN
Plain Craft Launcher/Images/Block-Add.png


BIN
Plain Craft Launcher/Images/Block-Anvil.png


BIN
Plain Craft Launcher/Images/Block-CobbleStone.png


BIN
Plain Craft Launcher/Images/Block-CommandBlock.png


BIN
Plain Craft Launcher/Images/Block-Dirt.png


BIN
Plain Craft Launcher/Images/Block-False.png


BIN
Plain Craft Launcher/Images/Block-Glass.png


BIN
Plain Craft Launcher/Images/Block-Grass.png


BIN
Plain Craft Launcher/Images/Block-RedstoneBlock.png


BIN
Plain Craft Launcher/Images/Block-Settings.png


BIN
Plain Craft Launcher/Images/Block-True.png


BIN
Plain Craft Launcher/Images/Checked-Black.png


BIN
Plain Craft Launcher/Images/Checked-Blue.png


BIN
Plain Craft Launcher/Images/Checked-Gray.png


BIN
Plain Craft Launcher/Images/False.png


BIN
Plain Craft Launcher/Images/Hint-Critical.png


BIN
Plain Craft Launcher/Images/Hint-Finish.png


BIN
Plain Craft Launcher/Images/Hint-Info.png


BIN
Plain Craft Launcher/Images/Hint-Warn.png


BIN
Plain Craft Launcher/Images/Infobox-Bg.png


BIN
Plain Craft Launcher/Images/LeftBar-BG_Black.png


BIN
Plain Craft Launcher/Images/LeftBar-BG_Blue.png


BIN
Plain Craft Launcher/Images/LeftBar-BG_DeathBlue.png


BIN
Plain Craft Launcher/Images/LeftBar-BG_Green.png


BIN
Plain Craft Launcher/Images/LeftBar-BG_Hunluan.png


BIN
Plain Craft Launcher/Images/LeftBar-BG_Orange.png


BIN
Plain Craft Launcher/Images/Stats-Fail.png


BIN
Plain Craft Launcher/Images/Stats-Finish.png


BIN
Plain Craft Launcher/Images/Stats-Retry.png


BIN
Plain Craft Launcher/Images/Stats-Running.png


BIN
Plain Craft Launcher/Images/Stats-Wait.png


BIN
Plain Craft Launcher/Images/True.png


BIN
Plain Craft Launcher/Images/alex.png


BIN
Plain Craft Launcher/Images/appbar.inbox.in.png


BIN
Plain Craft Launcher/Images/appbar.settings.png


BIN
Plain Craft Launcher/Images/icon.ico


BIN
Plain Craft Launcher/Images/imgMusic.png


BIN
Plain Craft Launcher/Images/imgTopClose.png


BIN
Plain Craft Launcher/Images/imgTopMin.png


BIN
Plain Craft Launcher/Images/infobox-blue.png


BIN
Plain Craft Launcher/Images/infobox-brown.png


BIN
Plain Craft Launcher/Images/infobox-green.png


BIN
Plain Craft Launcher/Images/infobox-orange.png


BIN
Plain Craft Launcher/Images/infobox-purple.png


BIN
Plain Craft Launcher/Images/infobox-yellow.png


BIN
Plain Craft Launcher/Images/steve.png


+ 58 - 0
Plain Craft Launcher/My Project/AssemblyInfo.vb

@@ -0,0 +1,58 @@
+Imports System.Reflection
+Imports System.Runtime.InteropServices
+Imports System.Resources
+
+' 有关程序集的常规信息通过下列特性集
+' 控制。更改这些特性值可修改
+' 与程序集关联的信息。
+
+' 查看程序集特性的值
+
+<Assembly: AssemblyTitle("Plain Craft Launcher")>
+<Assembly: AssemblyDescription("Minecraft 启动器 / 作者:龙腾猫跃")>
+<Assembly: AssemblyCompany("")>
+<Assembly: AssemblyProduct("")>
+<Assembly: AssemblyCopyright("Copyright © LTCat 2016 - 2019")>
+<Assembly: AssemblyTrademark("")>
+<Assembly: ComVisible(False)>
+
+'若要开始生成可本地化的应用程序,请
+'在您的 .vbproj 文件中的 <PropertyGroup> 内设置 <UICulture>CultureYouAreCodingWith</UICulture>。
+'例如,如果您在源文件中使用的是美国英语,
+'请将 <UICulture> 设置为“en-US”。然后取消下面对
+'NeutralResourceLanguage 特性的注释。更新下面行中的“en-US”
+'以与项目文件中的 UICulture 设置匹配。
+
+'<Assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)> 
+
+
+'ThemeInfo 特性说明在何处可以找到任何特定于主题的和一般性的资源词典。
+'第一个参数: 特定于主题的资源词典的位置
+'(在页面或应用程序资源词典中 
+' 未找到某个资源的情况下使用)
+
+'第二个参数: 一般性资源词典的位置
+'(未在页、应用程序和任何特定于主题的
+'资源词典中找到资源时使用)
+<Assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)>
+
+
+
+'如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+<Assembly: Guid("ab042783-e4d9-4e21-917c-10159df53b15")>
+
+' 程序集的版本信息由下面四个值组成:
+'
+'      主版本
+'      次版本
+'      内部版本号
+'      修订号
+'
+' 可以指定所有这些值,也可以使用“内部版本号”和“修订号”的默认值,
+' 方法是按如下所示使用“*”:
+' <Assembly: AssemblyVersion("1.0.*")> 
+
+<Assembly: AssemblyVersion("1.0.9.0")>
+<Assembly: AssemblyFileVersion("1.0.9.0")>
+
+<Assembly: NeutralResourcesLanguage("zh-Hans")>

+ 121 - 0
Plain Craft Launcher/My Project/MyExtensions/MyWpfExtension.vb

@@ -0,0 +1,121 @@
+#If _MyType <> "Empty" Then
+
+Namespace My
+    ''' <summary>
+    ''' 用于定义“我的 WPF 命名空间”中的可用属性的模块
+    ''' </summary>
+    ''' <remarks></remarks>
+    <Global.Microsoft.VisualBasic.HideModuleName()> _
+    Module MyWpfExtension
+        Private s_Computer As New ThreadSafeObjectProvider(Of Global.Microsoft.VisualBasic.Devices.Computer)
+        Private s_User As New ThreadSafeObjectProvider(Of Global.Microsoft.VisualBasic.ApplicationServices.User)
+        Private s_Windows As New ThreadSafeObjectProvider(Of MyWindows)
+        Private s_Log As New ThreadSafeObjectProvider(Of Global.Microsoft.VisualBasic.Logging.Log)
+        ''' <summary>
+        ''' 返回正在运行的应用程序的应用程序对象
+        ''' </summary>
+        <Global.System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")>  _
+        Friend ReadOnly Property Application() As Application
+            Get
+                Return CType(Global.System.Windows.Application.Current, Application)
+            End Get
+        End Property
+        ''' <summary>
+        ''' 返回有关主机计算机的信息。
+        ''' </summary>
+        <Global.System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")>  _
+        Friend ReadOnly Property Computer() As Global.Microsoft.VisualBasic.Devices.Computer
+            Get
+                Return s_Computer.GetInstance()
+            End Get
+        End Property
+        ''' <summary>
+        ''' 返回当前用户的信息。如果希望使用当前的 
+        ''' Windows 用户凭据来运行应用程序,请调用 My.User.InitializeWithWindowsUser()。
+        ''' </summary>
+        <Global.System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")>  _
+        Friend ReadOnly Property User() As Global.Microsoft.VisualBasic.ApplicationServices.User
+            Get
+                Return s_User.GetInstance()
+            End Get
+        End Property
+        ''' <summary>
+        ''' 返回应用程序日志。可以使用应用程序的配置文件配置侦听器。
+        ''' </summary>
+        <Global.System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")>  _
+        Friend ReadOnly Property Log() As Global.Microsoft.VisualBasic.Logging.Log
+            Get
+                Return s_Log.GetInstance()
+            End Get
+        End Property
+
+        ''' <summary>
+        ''' 返回项目中定义的 Windows 集合。
+        ''' </summary>
+        <Global.System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")>  _
+        Friend ReadOnly Property Windows() As MyWindows
+            <Global.System.Diagnostics.DebuggerHidden()> _
+            Get
+                Return s_Windows.GetInstance()
+            End Get
+        End Property
+        <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Never)> _
+        <Global.Microsoft.VisualBasic.MyGroupCollection("System.Windows.Window", "Create__Instance__", "Dispose__Instance__", "My.MyWpfExtenstionModule.Windows")> _
+        Friend NotInheritable Class MyWindows
+            <Global.System.Diagnostics.DebuggerHidden()> _
+            Private Shared Function Create__Instance__(Of T As {New, Global.System.Windows.Window})(ByVal Instance As T) As T
+                If Instance Is Nothing Then
+                    If s_WindowBeingCreated IsNot Nothing Then
+                        If s_WindowBeingCreated.ContainsKey(GetType(T)) = True Then
+                            Throw New Global.System.InvalidOperationException("The window cannot be accessed via My.Windows from the Window constructor.")
+                        End If
+                    Else
+                        s_WindowBeingCreated = New Global.System.Collections.Hashtable()
+                    End If
+                    s_WindowBeingCreated.Add(GetType(T), Nothing)
+                    Return New T()
+                    s_WindowBeingCreated.Remove(GetType(T))
+                Else
+                    Return Instance
+                End If
+            End Function
+            <Global.System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1822:MarkMembersAsStatic")>  _
+            <Global.System.Diagnostics.DebuggerHidden()> _
+            Private Sub Dispose__Instance__(Of T As Global.System.Windows.Window)(ByRef instance As T)
+                instance = Nothing
+            End Sub
+            <Global.System.Diagnostics.DebuggerHidden()> _
+            <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Never)> _
+            Public Sub New()
+                MyBase.New()
+            End Sub
+            <Global.System.ThreadStatic()> Private Shared s_WindowBeingCreated As Global.System.Collections.Hashtable
+            <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Never)> Public Overrides Function Equals(ByVal o As Object) As Boolean
+                Return MyBase.Equals(o)
+            End Function
+            <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Never)> Public Overrides Function GetHashCode() As Integer
+                Return MyBase.GetHashCode
+            End Function
+            <Global.System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1822:MarkMembersAsStatic")>  _
+            <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Never)> _
+            Friend Overloads Function [GetType]() As Global.System.Type
+                Return GetType(MyWindows)
+            End Function
+            <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Never)> Public Overrides Function ToString() As String
+                Return MyBase.ToString
+            End Function
+        End Class
+    End Module
+End Namespace
+Partial Class Application
+    Inherits Global.System.Windows.Application
+    <Global.System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")> _
+    <Global.System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1822:MarkMembersAsStatic")> _
+    Friend ReadOnly Property Info() As Global.Microsoft.VisualBasic.ApplicationServices.AssemblyInfo
+        <Global.System.Diagnostics.DebuggerHidden()> _
+        Get
+            Return New Global.Microsoft.VisualBasic.ApplicationServices.AssemblyInfo(Global.System.Reflection.Assembly.GetExecutingAssembly())
+        End Get
+    End Property
+End Class
+#End If

+ 103 - 0
Plain Craft Launcher/My Project/Resources.Designer.vb

@@ -0,0 +1,103 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+'     此代码由工具生成。
+'     运行时版本:4.0.30319.42000
+'
+'     对此文件的更改可能会导致不正确的行为,并且如果
+'     重新生成代码,这些更改将会丢失。
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict On
+Option Explicit On
+
+Imports System
+
+Namespace My.Resources
+    
+    '此类是由 StronglyTypedResourceBuilder
+    '类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+    '若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+    '(以 /str 作为命令选项),或重新生成 VS 项目。
+    '''<summary>
+    '''  一个强类型的资源类,用于查找本地化的字符串等。
+    '''</summary>
+    <Global.System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0"),  _
+     Global.System.Diagnostics.DebuggerNonUserCodeAttribute(),  _
+     Global.System.Runtime.CompilerServices.CompilerGeneratedAttribute(),  _
+     Global.Microsoft.VisualBasic.HideModuleNameAttribute()>  _
+    Friend Module Resources
+        
+        Private resourceMan As Global.System.Resources.ResourceManager
+        
+        Private resourceCulture As Global.System.Globalization.CultureInfo
+        
+        '''<summary>
+        '''  返回此类使用的缓存的 ResourceManager 实例。
+        '''</summary>
+        <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)>  _
+        Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager
+            Get
+                If Object.ReferenceEquals(resourceMan, Nothing) Then
+                    Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("pcl.Resources", GetType(Resources).Assembly)
+                    resourceMan = temp
+                End If
+                Return resourceMan
+            End Get
+        End Property
+        
+        '''<summary>
+        '''  使用此强类型资源类,为所有资源查找
+        '''  重写当前线程的 CurrentUICulture 属性。
+        '''</summary>
+        <Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)>  _
+        Friend Property Culture() As Global.System.Globalization.CultureInfo
+            Get
+                Return resourceCulture
+            End Get
+            Set
+                resourceCulture = value
+            End Set
+        End Property
+        
+        '''<summary>
+        '''  查找 System.Byte[] 类型的本地化资源。
+        '''</summary>
+        Friend ReadOnly Property Ionic_Zip() As Byte()
+            Get
+                Dim obj As Object = ResourceManager.GetObject("Ionic_Zip", resourceCulture)
+                Return CType(obj,Byte())
+            End Get
+        End Property
+        
+        '''<summary>
+        '''  查找 System.Byte[] 类型的本地化资源。
+        '''</summary>
+        Friend ReadOnly Property Newtonsoft_Json() As Byte()
+            Get
+                Dim obj As Object = ResourceManager.GetObject("Newtonsoft_Json", resourceCulture)
+                Return CType(obj,Byte())
+            End Get
+        End Property
+        
+        '''<summary>
+        '''  查找 System.Byte[] 类型的本地化资源。
+        '''</summary>
+        Friend ReadOnly Property PCLAM() As Byte()
+            Get
+                Dim obj As Object = ResourceManager.GetObject("PCLAM", resourceCulture)
+                Return CType(obj,Byte())
+            End Get
+        End Property
+        
+        '''<summary>
+        '''  查找 System.Byte[] 类型的本地化资源。
+        '''</summary>
+        Friend ReadOnly Property UpdateLog() As Byte()
+            Get
+                Dim obj As Object = ResourceManager.GetObject("UpdateLog", resourceCulture)
+                Return CType(obj,Byte())
+            End Get
+        End Property
+    End Module
+End Namespace

+ 133 - 0
Plain Craft Launcher/My Project/Resources.resx

@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+  <data name="Ionic_Zip" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Ionic.Zip.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="Newtonsoft_Json" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Newtonsoft.Json.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="PCLAM" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Plain Craft Launcher Admin Manager.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="UpdateLog" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\UpdateLog.txt;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+</root>

+ 71 - 0
Plain Craft Launcher/My Project/Settings.Designer.vb

@@ -0,0 +1,71 @@
+'------------------------------------------------------------------------------
+' <auto-generated>
+'     此代码由工具生成。
+'     运行时版本:4.0.30319.42000
+'
+'     对此文件的更改可能会导致不正确的行为,并且如果
+'     重新生成代码,这些更改将会丢失。
+' </auto-generated>
+'------------------------------------------------------------------------------
+
+Option Strict On
+Option Explicit On
+
+
+
+<Global.System.Runtime.CompilerServices.CompilerGeneratedAttribute(),  _
+ Global.System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0"),  _
+ Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)>  _
+Partial Friend NotInheritable Class Settings
+    Inherits Global.System.Configuration.ApplicationSettingsBase
+    
+    Private Shared defaultInstance As Settings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New Settings()),Settings)
+    
+#Region "My.Settings 自动保存功能"
+#If _MyType = "WindowsForms" Then
+    Private Shared addedHandler As Boolean
+
+    Private Shared addedHandlerLockObject As New Object
+
+    <Global.System.Diagnostics.DebuggerNonUserCodeAttribute(), Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
+    Private Shared Sub AutoSaveSettings(ByVal sender As Global.System.Object, ByVal e As Global.System.EventArgs)
+        If My.Application.SaveMySettingsOnExit Then
+            My.Settings.Save()
+        End If
+    End Sub
+#End If
+#End Region
+    
+    Public Shared ReadOnly Property [Default]() As Settings
+        Get
+            
+#If _MyType = "WindowsForms" Then
+               If Not addedHandler Then
+                    SyncLock addedHandlerLockObject
+                        If Not addedHandler Then
+                            AddHandler My.Application.Shutdown, AddressOf AutoSaveSettings
+                            addedHandler = True
+                        End If
+                    End SyncLock
+                End If
+#End If
+            Return defaultInstance
+        End Get
+    End Property
+End Class
+
+Namespace My
+    
+    <Global.Microsoft.VisualBasic.HideModuleNameAttribute(),  _
+     Global.System.Diagnostics.DebuggerNonUserCodeAttribute(),  _
+     Global.System.Runtime.CompilerServices.CompilerGeneratedAttribute()>  _
+    Friend Module MySettingsProperty
+        
+        <Global.System.ComponentModel.Design.HelpKeywordAttribute("My.Settings")>  _
+        Friend ReadOnly Property Settings() As Global.pcl.Settings
+            Get
+                Return Global.pcl.Settings.Default
+            End Get
+        End Property
+    End Module
+End Namespace

+ 7 - 0
Plain Craft Launcher/My Project/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" UseMySettingsClassName="True">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 48 - 0
Plain Craft Launcher/My Project/app.manifest

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <!-- UAC 清单选项
+            如果要更改 Windows 用户帐户控制级别,请用以下节点之一替换 
+            requestedExecutionLevel 节点。
+
+        <requestedExecutionLevel  level="asInvoker" uiAccess="False" />
+        <requestedExecutionLevel  level="requireAdministrator" uiAccess="False" />
+        <requestedExecutionLevel  level="highestAvailable" uiAccess="False" />
+
+            指定 requestedExecutionLevel 节点将会禁用文件和注册表虚拟化。
+            如果要利用文件和注册表虚拟化实现向后
+            兼容性,则删除 requestedExecutionLevel 节点。
+        -->
+        <requestedExecutionLevel level="asInvoker" uiAccess="False" />
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+  
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- 此应用程序设计使用的所有 Windows 版本的列表。Windows 将会自动选择最兼容的环境。-->
+
+      <!-- 如果应用程序设计使用 Windows 7,请取消注释以下 supportedOS 节点-->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+      
+    </application>
+  </compatibility>
+  
+  <!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
+  <dependency>
+    <dependentAssembly>
+      <assemblyIdentity
+          type="win32"
+          name="Microsoft.Windows.Common-Controls"
+          version="6.0.0.0"
+          processorArchitecture="*"
+          publicKeyToken="6595b64144ccf1df"
+          language="*"
+        />
+    </dependentAssembly>
+  </dependency>
+
+</asmv1:assembly>

+ 51 - 0
Plain Craft Launcher/Pages/formDownloadLeft.xaml

@@ -0,0 +1,51 @@
+<Window
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:my="clr-namespace:pcl"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="formDownloadLeft"
+    Title="frmHomeDownload" FontFamily="Microsoft YaHei">
+    <Grid x:Name="panMain" Height="323" VerticalAlignment="Top" Width="731">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto" />
+            <RowDefinition Height="263*" />
+        </Grid.RowDefinitions>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="Auto" />
+            <ColumnDefinition Width="Auto" />
+            <ColumnDefinition Width="Auto" />
+            <ColumnDefinition Width="321*" />
+        </Grid.ColumnDefinitions>
+        <Label Content="加载中" x:Name="labLoading" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="19" Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="2" Foreground="{DynamicResource Color3}" Margin="9,0,0,0" d:IsHidden="True" />
+
+        <my:Selecter Foreground="{DynamicResource ResourceKey=Color4}" HorizontalAlignment="Left" Margin="0,9,0,0" x:Name="selecter" VerticalAlignment="Top" Grid.RowSpan="2" Padding="0,0,12,0" />
+        <my:Timer HorizontalAlignment="Left" Margin="107,176,0,0" x:Name="timerLoad" VerticalAlignment="Top" Interval="100" Grid.Column="3" Grid.Row="1" />
+        <Grid Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="2" x:Name="panFinish" Margin="9,0,0,0" Visibility="Hidden">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="Auto" />
+                <RowDefinition Height="263*" />
+            </Grid.RowDefinitions>
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="Auto" />
+                <ColumnDefinition Width="Auto" />
+                <ColumnDefinition Width="321*" />
+            </Grid.ColumnDefinitions>
+            <Label x:Name="labRight" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="19" Grid.Column="3" Grid.RowSpan="2" Foreground="{DynamicResource Color3}" />
+            <Grid Grid.Column="2" Margin="0,9,0,0" UseLayoutRounding="True" x:Name="panNew">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="160*" />
+                    <ColumnDefinition Width="Auto" />
+                </Grid.ColumnDefinitions>
+                <my:ListItem x:Name="item1" SubText="最新正式版" ButtonLogo="/pcl;component/Images/appbar.inbox.in.png" Logo="/pcl;component/Images/Block-Grass.png" CanCheck="False" UseLayoutRounding="True" Margin="0,0,0,9" />
+                <my:ListItem x:Name="item2" Width="200" SubText="最新测试版" ButtonLogo="/pcl;component/Images/appbar.inbox.in.png" Logo="/pcl;component/Images/Block-CommandBlock.png" CanCheck="False" Grid.Column="1" UseLayoutRounding="True" Margin="9,0,0,9" />
+            </Grid>
+            <Grid HorizontalAlignment="Left" x:Name="panForgeHost" Margin="0,9,0,0" Width="120" Grid.RowSpan="2" Background="#01FFFFFF" Height="305" VerticalAlignment="Top" Visibility="Collapsed">
+                <StackPanel x:Name="panForge" VerticalAlignment="Top"/>
+            </Grid>
+            <my:VScroll Foreground="{DynamicResource ResourceKey=Color2}" Margin="126,9,0,9" x:Name="scrollForge" Visibility="Collapsed" Grid.RowSpan="2" HorizontalAlignment="Left" Padding="0,0,9,0" Opacity="0" />
+            <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="panVersionHost" Margin="0,0,20,9" Grid.Column="2" Grid.Row="1" Background="#01FFFFFF">
+                <StackPanel x:Name="panVersion" VerticalAlignment="Top"/>
+            </Grid>
+            <my:VScroll Foreground="{DynamicResource ResourceKey=Color2}" HorizontalAlignment="Right" Margin="0,0,0,9" x:Name="scrollVersion" Width="10" Grid.Column="2" Grid.Row="1" Opacity="0" />
+        </Grid>
+    </Grid>
+</Window>

+ 1043 - 0
Plain Craft Launcher/Pages/formDownloadLeft.xaml.vb

@@ -0,0 +1,1043 @@
+Imports Ionic.Zip
+
+Public Class formDownloadLeft
+
+    '用于加载的Timer
+    Private Sub timerLoad_Tick() Handles timerLoad.Tick
+        Try
+
+            '获取基本信息
+            Dim NowState As LoadState
+            Select Case selecter.SelectIndexName
+                Case "Minecraft"
+                    NowState = MinecraftState
+                Case "OptiFine"
+                    NowState = OptiFineState
+                Case "Forge"
+                    NowState = If(ForgeVersionState = LoadState.Loaded, ForgeState, ForgeVersionState)
+                Case Else
+                    Exit Sub
+            End Select
+
+            If selecter.SelectIndexName = "Forge" And ForgeVersionState = LoadState.Loaded Then
+                'Forge状态处理
+                Select Case NowState
+                    Case LoadState.Loaded
+                        Exit Sub
+                    Case LoadState.Loading
+                        labRight.Content = "加载中"
+                        Exit Sub
+                    Case LoadState.Failed
+                        labRight.Content = "加载失败"
+                        Exit Sub
+                    Case LoadState.NoConnection
+                        labRight.Content = "没有网络连接"
+                        Exit Sub
+                    Case LoadState.Success
+                        labRight.Content = ""
+                End Select
+            Else
+                '基本事态处理,这里自动控制了两个主UI的切换
+                Select Case NowState
+                    Case LoadState.Loaded
+                        Exit Sub
+                    Case LoadState.Loading
+                        labLoading.Content = "加载中"
+                        labLoading.Visibility = Visibility.Visible
+                        panFinish.Visibility = Visibility.Hidden
+                        Exit Sub
+                    Case LoadState.Failed
+                        labLoading.Content = "加载失败,点击以重试"
+                        labLoading.Visibility = Visibility.Visible
+                        panFinish.Visibility = Visibility.Hidden
+                        Exit Sub
+                    Case LoadState.NoConnection
+                        labLoading.Content = "没有网络连接"
+                        labLoading.Visibility = Visibility.Visible
+                        panFinish.Visibility = Visibility.Hidden
+                        Exit Sub
+                    Case LoadState.Success
+                        '开始加载,把控件显示出来
+                        labLoading.Visibility = Visibility.Collapsed
+                        panFinish.Visibility = Visibility.Visible
+                End Select
+            End If
+
+            '加载
+            Select Case selecter.SelectIndexName
+                Case "Minecraft"
+                    '结束显示
+                    MinecraftState = LoadState.Loaded
+                    '加载预览版,预览版总是列表第一个
+                    item2.MainText = MinecraftArray(0).id
+                    item2.Tag = MinecraftArray(0)
+                    '是否加载正式版的标记
+                    Dim item1Loaded As Boolean = False
+                    '循环添加
+                    For Each ver As MinecraftVersion In MinecraftArray
+                        '判断正式版
+                        If ver.type = "release" And Not item1Loaded Then
+                            item1Loaded = True
+                            item1.MainText = ver.id
+                            item1.Tag = ver
+                        End If
+                        '判断Logo样式
+                        Dim Photo As String = "Grass"
+                        If ver.type = "snapshot" Then Photo = "CommandBlock"
+                        If ver.type = "fool" Then Photo = "Dirt"
+                        If ver.type.Contains("old") Then Photo = "CobbleStone"
+                        '添加控件
+                        Dim item As ListItem = New ListItem With {.UseLayoutRounding = True, .Tag = ver, .CanCheck = False, .ButtonLogo = New BitmapImage(New Uri("/Images/appbar.inbox.in.png", UriKind.Relative)), .MainText = ver.id, .SubText = ver.time, .Name = "list" & GetUUID(), .Logo = New BitmapImage(New Uri(PATH_IMAGE & "Block-" & Photo & ".png", UriKind.Absolute)), .ToolTip = If(Photo = "Dirt", "该版本由 PCL 特别提供", Nothing)}
+                        AddHandler item.ButtonClick, AddressOf MinecraftDownloadClick
+                        panVersion.Children.Add(item)
+                    Next
+                    '最新正式版与最新预览版一样则隐藏一个
+                    If item1.MainText = item2.MainText Then
+                        item2.Visibility = Visibility.Collapsed
+                    Else
+                        item2.Visibility = Visibility.Visible
+                    End If
+                Case "OptiFine"
+                    '结束显示
+                    OptiFineState = LoadState.Loaded
+                    '加载预览版,预览版总是列表第一个
+                    item2.MainText = OptiFineArray(0).id.ToString
+                    item2.Tag = OptiFineArray(0)
+                    '是否加载正式版的标记
+                    Dim item1Loaded = False
+                    For Each ver As OptiFineVersion In OptiFineArray
+                        '判断正式版
+                        If Not (ver.id.Contains("pre") Or item1Loaded) Then
+                            item1Loaded = True
+                            item1.MainText = ver.id
+                            item1.Tag = ver
+                        End If
+                        '判断Logo样式
+                        Dim Photo = If(ver.id.Contains("pre"), "CommandBlock", "Grass")
+                        '添加控件
+                        Dim item As ListItem = New ListItem With {.UseLayoutRounding = True, .Tag = ver, .CanCheck = False, .ButtonLogo = New BitmapImage(New Uri("/Images/appbar.inbox.in.png", UriKind.Relative)), .MainText = ver.id, .SubText = ver.time, .Name = "list" & GetUUID(), .Logo = New BitmapImage(New Uri("/Images/Block-" & Photo & ".png", UriKind.Relative))}
+                        AddHandler item.ButtonClick, AddressOf OptiFineDownloadStart
+                        panVersion.Children.Add(item)
+                    Next
+                    '最新正式版与最新预览版一样则隐藏一个
+                    If item1.MainText = item2.MainText Then
+                        item2.Visibility = Visibility.Collapsed
+                    Else
+                        item2.Visibility = Visibility.Visible
+                    End If
+                    ''使用 BMCLAPI 获取 OptiFine 源时进行隐藏
+                    'If selecter.SelectIndexName = "OptiFine" Then
+                    '    If OptiFineArray.Count > 0 Then
+                    '        item1.Visibility = If(OptiFineArray(0).time = "", Visibility.Collapsed, Visibility.Visible)
+                    '        item2.Visibility = If(OptiFineArray(0).time = "", Visibility.Collapsed, Visibility.Visible)
+                    '    End If
+                    'End If
+                Case "Forge"
+                    If ForgeVersionState = LoadState.Success Then 'ForgeVersion
+                        '结束显示
+                        ForgeVersionState = LoadState.Loaded
+                        '加载列表
+                        panForge.Children.Clear()
+                        For Each Version As String In ForgeVersionArray
+                            '添加控件
+                            Dim item As ListItem = New ListItem With {.SubText = "", .ShowButton = False, .UseLayoutRounding = True, .CanCheck = True, .MainText = Version, .Name = "list" & GetUUID(), .Logo = New BitmapImage(New Uri("/Images/Block-Grass.png", UriKind.Relative))}
+                            AddHandler item.Change, AddressOf ChangeForgeVersionSelection
+                            panForge.Children.Add(item)
+                        Next
+                        '引发第一个的改变事件
+                        If panForge.Children.Count > 0 Then
+                            CType(panForge.Children(0), ListItem).Checked = True
+                            ChangeForgeVersionSelection(CType(panForge.Children(0), ListItem), Nothing)
+                        End If
+                        AniStart(AaStack(panForge), "DownloadLeftShowForge")
+                        scrollForge.Value = 0
+                        scrollVersion.Visibility = Visibility.Hidden
+                    Else 'Forge
+                        panVersionHost.Visibility = Visibility.Collapsed
+                        scrollVersion.Visibility = Visibility.Visible
+                        Try
+                            '结束显示
+                            ForgeState = LoadState.Loaded
+                            labRight.Content = ""
+                            Dim array = ForgeArray
+                            panVersion.Children.Clear()
+                            For Each ver As ForgeVersion In array
+                                Dim item As ListItem = New ListItem With {.UseLayoutRounding = True, .Tag = ver, .CanCheck = False, .ButtonLogo = New BitmapImage(New Uri("/Images/appbar.inbox.in.png", UriKind.Relative)), .MainText = "Forge " & ver.version, .SubText = ver.time, .Name = "list" & GetUUID(), .Logo = New BitmapImage(New Uri("/Images/Block-Anvil.png", UriKind.Relative))}
+                                AddHandler item.ButtonClick, AddressOf ForgeDownloadStart
+                                panVersion.Children.Add(item)
+                            Next
+                        Catch
+                            '时不时会出现集合已修改的Exception,事实证明重新加载一次就成了,管它啥原因嘞
+                            ForgeState = LoadState.Success
+                        End Try
+                    End If
+                    panVersionHost.Visibility = Visibility.Visible
+            End Select
+
+        Catch ex As Exception
+            ExShow(ex, "下载列表检测时钟出错", ErrorLevel.Slient)
+        End Try
+    End Sub
+
+    '改变左边选择条
+    Private Sub ChangeSelection(ByVal Selection As String) Handles selecter.SelectionChange
+        '初始化滚动条
+        panVersion.Children.Clear()
+        scrollVersion.ValueMe = 0
+        '初始化页面
+        labLoading.Content = "加载中"
+        labLoading.Visibility = Visibility.Visible
+        panFinish.Visibility = Visibility.Hidden
+        scrollVersion.Visibility = Visibility.Visible
+        '分情况处理
+        Select Case Selection
+            Case "Minecraft"
+                '状态改变
+                Select Case MinecraftState
+                    Case LoadState.Loaded
+                        '已经加载过了,需要刷新显示
+                        MinecraftState = LoadState.Success
+                    Case LoadState.Failed
+                        '加载失败,自动刷新
+                        MinecraftState = LoadState.Loading
+                        Pool.Add(New Thread(Sub() GetMinecraftBasic(True)))
+                End Select
+                '对应页面
+                panForgeHost.Visibility = Visibility.Collapsed
+                scrollForge.Visibility = Visibility.Collapsed
+                item1.Visibility = Visibility.Visible
+                item2.Visibility = Visibility.Visible
+                labRight.Content = ""
+            Case "OptiFine"
+                '状态改变
+                Select Case OptiFineState
+                    Case LoadState.Loaded
+                        '已经加载过了,需要刷新显示
+                        OptiFineState = LoadState.Success
+                    Case LoadState.Failed
+                        '加载失败,自动刷新
+                        OptiFineState = LoadState.Loading
+                        Pool.Add(New Thread(AddressOf GetOptiFineBasic))
+                End Select
+                '对应页面
+                panForgeHost.Visibility = Visibility.Collapsed
+                scrollForge.Visibility = Visibility.Collapsed
+                item1.Visibility = Visibility.Visible
+                item2.Visibility = Visibility.Visible
+                labRight.Content = ""
+            Case "Forge"
+                '状态改变
+                Select Case ForgeVersionState
+                    Case LoadState.Loaded
+                        '已经加载过了,需要刷新显示
+                        ForgeVersionState = LoadState.Success
+                    Case LoadState.Failed
+                        '加载失败,自动刷新
+                        ForgeVersionState = LoadState.Loading
+                        Pool.Add(New Thread(AddressOf GetForgeVersionBasic))
+                End Select
+                '对应页面
+                panForgeHost.Visibility = Visibility.Visible
+                scrollForge.Visibility = Visibility.Visible
+                item1.Visibility = Visibility.Collapsed
+                item2.Visibility = Visibility.Collapsed
+                labRight.Content = "加载中"
+        End Select
+    End Sub
+
+    '点击重试
+    Private Sub labLoading_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles labLoading.MouseDown
+        Select Case selecter.SelectIndexName
+            Case "Minecraft"
+                If MinecraftState = LoadState.Failed Then
+                    Dim th As New Thread(Sub() GetMinecraftBasic(True))
+                    th.Start()
+                End If
+            Case "OptiFine"
+                If OptiFineState = LoadState.Failed Then
+                    Dim th As New Thread(AddressOf GetOptiFineBasic)
+                    th.Start()
+                End If
+            Case "Forge"
+                If ForgeVersionState = LoadState.Failed Then
+                    Dim th As New Thread(AddressOf GetForgeVersionBasic)
+                    th.Start()
+                End If
+        End Select
+    End Sub
+
+    '加载的状态
+    Public Enum LoadState As Byte
+        ''' <summary>
+        ''' 正在加载
+        ''' </summary>
+        ''' <remarks></remarks>
+        Loading = 0
+        ''' <summary>
+        ''' 已经加载结束,但是没有显示
+        ''' </summary>
+        ''' <remarks></remarks>
+        Success = 1
+        ''' <summary>
+        ''' 加载失败
+        ''' </summary>
+        ''' <remarks></remarks>
+        Failed = 2
+        ''' <summary>
+        ''' 加载失败(没有联网)
+        ''' </summary>
+        ''' <remarks></remarks>
+        NoConnection = 3
+        ''' <summary>
+        ''' 已经加载结束,并且已显示,此时不执行Timer代码
+        ''' </summary>
+        ''' <remarks></remarks>
+        Loaded = 4
+    End Enum
+
+#Region "窗体基础"
+
+    Dim PageLoaded As Boolean = False
+    Private Sub panVersion_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles panVersion.Loaded
+        Dim EnabledList As New ArrayList
+        If ReadIni("setup", "UiHiddenMinecraft", "False") = "False" Then EnabledList.Add("Minecraft")
+        If ReadIni("setup", "UiHiddenOptiFine", "False") = "False" Then EnabledList.Add("OptiFine")
+        If ReadIni("setup", "UiHiddenForge", "False") = "False" Then EnabledList.Add("Forge")
+        Select Case EnabledList.Count
+            Case 0
+                selecter.ShowList = {"Nothing"}
+                panMain.Visibility = Visibility.Collapsed
+            Case 1
+                selecter.ShowList = EnabledList.ToArray
+                selecter.Visibility = Visibility.Collapsed
+                panMain.Visibility = Visibility.Visible
+            Case 2, 3
+                selecter.ShowList = EnabledList.ToArray
+                selecter.Visibility = Visibility.Visible
+                panMain.Visibility = Visibility.Visible
+        End Select
+        If Not PageLoaded Then
+            PageLoaded = True
+            panMain.UpdateLayout()
+        End If
+        If scrollVersion.SetControl(panVersion, True) Then AddHandler panVersionHost.MouseWheel, AddressOf scrollVersion.RunMouseWheel
+    End Sub
+    Private Sub panForge_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles panForge.Loaded
+        If scrollForge.SetControl(panForge, True) Then AddHandler panForgeHost.MouseWheel, AddressOf scrollForge.RunMouseWheel
+    End Sub
+
+    Private Sub panNew_SizeChanged(ByVal sender As Object, ByVal e As System.Windows.SizeChangedEventArgs) Handles panNew.SizeChanged
+        item2.Width = Int((panNew.ActualWidth - 9) / 2)
+    End Sub
+
+#End Region
+
+#Region "Minecraft"
+
+    Public MinecraftState As LoadState = LoadState.Failed '加载状态
+    Public MinecraftArray As New ArrayList '代码分析的数组
+    Public MinecraftInfo As String = "" '获取的代码
+    Public Structure MinecraftVersion
+        Dim id As String
+        Dim type As String
+        Dim url As String
+        Dim time As String
+    End Structure
+    Public Sub GetMinecraftBasic(ByVal IsShowHint As Boolean)
+
+        '初始化
+        MinecraftState = LoadState.Loading
+        MinecraftArray = New ArrayList
+        If MODE_OFFLINE Then
+            MinecraftState = LoadState.NoConnection
+            Exit Sub
+        End If
+
+        '获取版本列表
+        Try
+            If ReadIni("setup", "DownVersion", "0") = "0" Then
+                MinecraftInfo = GetWebsiteCode("https://launchermeta.mojang.com/mc/game/version_manifest.json", Encoding.Default)
+                If MinecraftInfo = "" Then
+                    MinecraftInfo = GetWebsiteCode("http://bmclapi2.bangbang93.com/mc/game/version_manifest.json", Encoding.Default)
+                    If IsShowHint And Not MinecraftInfo = "" Then ShowHint("获取官方 Minecraft 版本列表失败,自动切换为 BMCLAPI")
+                End If
+            Else
+                MinecraftInfo = GetWebsiteCode("http://bmclapi2.bangbang93.com/mc/game/version_manifest.json", Encoding.Default)
+                If MinecraftInfo = "" Then
+                    MinecraftInfo = GetWebsiteCode("https://launchermeta.mojang.com/mc/game/version_manifest.json", Encoding.Default)
+                End If
+            End If
+            If Len(MinecraftInfo) < 300 Then Throw New WebException("获取到的列表长度不足:" & MinecraftInfo)
+        Catch ex As Exception
+            ExShow(ex, "获取 Minecraft 版本列表失败")
+            MinecraftInfo = ""
+            MinecraftState = LoadState.Failed
+            Exit Sub
+        End Try
+        log("[DownloadLeft] 获取 Minecraft 版本列表成功")
+
+        '加载版本列表信息
+        Try
+            log("[DownloadLeft] 加载 Minecraft 版本列表信息开始")
+            '预处理
+            Dim ids As ArrayList = RegexSearch(MinecraftInfo, "(?<=id"": "")[^""]+")
+            ids.AddRange(RegexSearch(MinecraftInfo, "(?<=id"":"")[^""]+"))
+            Dim urls As ArrayList = RegexSearch(MinecraftInfo, "(?<=url"": "")[^""]+")
+            urls.AddRange(RegexSearch(MinecraftInfo, "(?<=url"":"")[^""]+"))
+            Dim times As ArrayList = RegexSearch(MinecraftInfo, "(?<=releaseTime"": "")[^T]+")
+            times.AddRange(RegexSearch(MinecraftInfo, "(?<=releaseTime"":"")[^T]+"))
+            Dim types As ArrayList = RegexSearch(MinecraftInfo, "(?<=type"": "")[^""]+")
+            types.AddRange(RegexSearch(MinecraftInfo, "(?<=type"":"")[^""]+"))
+            '长度检查
+            If Not (ids.Count = urls.Count And urls.Count = times.Count And times.Count = types.Count) Then Throw New WebException("获取到的列表长度不等")
+            '添加
+            For i = 0 To ids.Count - 1
+                MinecraftArray.Add(New MinecraftVersion With {.id = ids(i), .url = urls(i), .time = times(i), .type = types(i)})
+            Next
+            '确认最新版本
+            If MinecraftArray.Count > 0 Then
+                Select Case ReadIni("setup", "LastMinecraftVersion")
+                    Case ""
+                        '没有执行过,不提醒
+                        WriteIni("setup", "LastMinecraftVersion", CType(MinecraftArray(0), MinecraftVersion).id)
+                    Case CType(MinecraftArray(0), MinecraftVersion).id
+                        '相同,不提醒
+                    Case Else
+                        '不相同,提醒
+                        WriteIni("setup", "LastMinecraftVersion", CType(MinecraftArray(0), MinecraftVersion).id)
+                        If ReadIni("setup", "HomeUpdate", "True") = "True" Then
+                            If Not (ReadIni("setup", "HomeUpdateRelease", "False") = "True" And (CType(MinecraftArray(0), MinecraftVersion).id.Contains("w") Or CType(MinecraftArray(0), MinecraftVersion).id.Contains("pre"))) Then
+                                frmHomeRight.Dispatcher.Invoke(Sub() frmHomeRight.ShowUpdate("发现游戏更新:" & CType(MinecraftArray(0), MinecraftVersion).id, formHomeRight.UpdateType.MINECRAFT))
+                            End If
+                        End If
+                End Select
+            End If
+        Catch ex As Exception
+            ExShow(ex, "加载 Minecraft 版本列表信息失败")
+            MinecraftInfo = ""
+            MinecraftState = LoadState.Failed
+            Exit Sub
+        End Try
+
+        log("[DownloadLeft] 加载 Minecraft 版本列表信息成功")
+        MinecraftState = LoadState.Success
+
+    End Sub
+
+    Private Sub MinecraftDownloadClick(ByVal sender As ListItem, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles item1.ButtonClick, item2.ButtonClick
+        If IsNothing(sender.Tag) Or Not selecter.SelectIndexName = "Minecraft" Then Exit Sub
+        MinecraftDownloadStart(sender.Tag)
+    End Sub
+    Private Sub MinecraftDownloadStart(Ver As MinecraftVersion)
+        Dim realID As String = Ver.id
+        Dim local As String = PATH_MC & "versions\" & realID & "\"
+        If WebGroups.ContainsKey("Minecraft " & realID) Or WebGroups.ContainsKey("Minecraft " & realID & " 信息") Then
+            ShowHint(New HintConverter("正在下载中,请勿重复操作", HintState.Warn))
+            Exit Sub
+        End If
+        If Not Directory.Exists(local) Then Directory.CreateDirectory(local)
+        '检测已存在版本
+        If GetFileSize(local & realID & ".jar") > 1024 Then
+            If MyMsgbox("该版本已存在,是否要删除当前版本并且重新下载?", "提示", "确定", "取消") = 1 Then
+                Try
+                    File.Delete(local & realID & ".json")
+                    File.Delete(local & realID & ".jar")
+                Catch ex As Exception
+                    ExShow(ex, "删除版本失败", ErrorLevel.MsgboxWithoutFeedback)
+                End Try
+            Else
+                Exit Sub
+            End If
+        End If
+        '下载
+        log("[DownloadLeft] 下载版本:" & realID)
+        ShowHint("Minecraft " & realID & " 开始下载")
+        SendStat("下载", "Minecraft", realID)
+        Dim th As New Thread(Sub()
+                                 Try
+                                     Directory.CreateDirectory(local)
+                                     Dim DownloadList As ArrayList
+                                     If ReadIni("setup", "DownMinecraft", "1") = "0" Then
+                                         '官方源优先
+                                         DownloadList = New ArrayList({
+                                                     Ver.url.Replace("https://launcher.mojang.com", "http://bmclapi2.bangbang93.com").Replace("https://launchermeta.mojang.com", "http://bmclapi2.bangbang93.com"),
+                                                     Ver.url
+                                                 })
+                                     Else
+                                         'BMCLAPI 优先
+                                         DownloadList = New ArrayList({
+                                                     Ver.url,
+                                                     Ver.url.Replace("https://launcher.mojang.com", "http://bmclapi2.bangbang93.com").Replace("https://launchermeta.mojang.com", "http://bmclapi2.bangbang93.com")
+                                                 })
+                                     End If
+                                     WebStart({
+                                              New WebRequireFile With {.WebURLs = DownloadList, .LocalFolder = local, .LocalName = realID & ".json", .KnownFileSize = 1024 * 2}}, "Minecraft " & realID & " 信息",
+                                              Sub()
+                                                  Try
+
+                                                      Dim json As JObject = Newtonsoft.Json.JsonConvert.DeserializeObject(ReadFileToEnd(local & realID & ".json"))
+                                                      Dim size As Integer
+                                                      Try
+                                                          size = json("downloads")("client")("size").ToString
+                                                      Catch
+                                                          size = 1024 * 50
+                                                      End Try
+                                                      Dim url As New ArrayList
+                                                      If ReadIni("setup", "DownMinecraft", "1") = "0" Then
+                                                          'Mojang 优先
+                                                          url.Add(json("downloads")("client")("url").ToString)
+                                                          url.Add(json("downloads")("client")("url").ToString.Replace("https://launcher.mojang.com", "http://bmclapi2.bangbang93.com").Replace("https://launchermeta.mojang.com", "http://bmclapi2.bangbang93.com"))
+                                                          url.Add(json("downloads")("client")("url").ToString)
+                                                          url.Add(json("downloads")("client")("url").ToString.Replace("https://launcher.mojang.com", "http://bmclapi2.bangbang93.com").Replace("https://launchermeta.mojang.com", "http://bmclapi2.bangbang93.com"))
+                                                      Else
+                                                          'BMCLAPI 优先
+                                                          url.Add(json("downloads")("client")("url").ToString.Replace("https://launcher.mojang.com", "http://bmclapi2.bangbang93.com").Replace("https://launchermeta.mojang.com", "http://bmclapi2.bangbang93.com"))
+                                                          url.Add(json("downloads")("client")("url").ToString)
+                                                          url.Add(json("downloads")("client")("url").ToString.Replace("https://launcher.mojang.com", "http://bmclapi2.bangbang93.com").Replace("https://launchermeta.mojang.com", "http://bmclapi2.bangbang93.com"))
+                                                          url.Add(json("downloads")("client")("url").ToString)
+                                                      End If
+                                                      WebStart({New WebRequireFile With {.WebURLs = url, .LocalFolder = local, .LocalName = realID & ".jar", .KnownFileSize = size}}, "Minecraft " & realID, AddressOf MinecraftDownloadSuccess, AddressOf MinecraftDownloadFail, If(size = 1024 * 50, WebRequireSize.AtLeast, WebRequireSize.Known))
+
+                                                  Catch ex As Exception
+                                                      ExShow(ex, "下载版本失败:" & realID, ErrorLevel.AllUsers)
+                                                      Try
+                                                          File.Delete(local & realID & ".json")
+                                                          File.Delete(local & realID & ".jar")
+                                                      Catch : End Try
+                                                      Application.Current.Dispatcher.Invoke(CType(AddressOf MinecraftDownloadFail, ParameterizedThreadStart), realID)
+                                                  End Try
+                                              End Sub,
+                                              AddressOf MinecraftDownloadFail,
+                                              WebRequireSize.AtLeast)
+                                 Catch ex As Exception
+                                     ExShow(ex, "下载版本失败:" & realID, ErrorLevel.AllUsers)
+                                     Try
+                                         File.Delete(local & realID & ".json")
+                                         File.Delete(local & realID & ".jar")
+                                     Catch : End Try
+                                     Application.Current.Dispatcher.Invoke(CType(AddressOf MinecraftDownloadFail, ParameterizedThreadStart), realID)
+                                 End Try
+                             End Sub)
+        th.Start()
+    End Sub
+    Private Sub MinecraftDownloadSuccess(ByVal Name As String)
+        Try
+            Pool.Add(New Thread(AddressOf PoolVersionList))
+            ShowHint(New HintConverter(Name & " 下载成功", HintState.Finish))
+        Catch ex As Exception
+            ExShow(ex, "Minecraft 下载结束后的处理异常:" & Name)
+            ShowHint(New HintConverter(Name & " 下载失败:" & GetStringFromException(ex) & "!", HintState.Critical))
+        End Try
+    End Sub
+    Private Sub MinecraftDownloadFail(ByVal Name As String)
+        ShowHint(New HintConverter(Name & " 下载失败", HintState.Critical))
+    End Sub
+
+    Private Function MinecraftGet(VersionName As String) As MinecraftVersion
+        If MinecraftArray.Count = 0 Then Return Nothing
+        If VersionName.EndsWith(".0") And VersionName.StartsWith("1.") Then VersionName = Mid(VersionName, 1, Len(VersionName) - 2)
+
+        '获取对应版本
+        Dim SelectVersion As MinecraftVersion = Nothing
+        For Each Ver As MinecraftVersion In MinecraftArray
+            If Ver.id = VersionName Then
+                SelectVersion = Ver
+                Exit For
+            End If
+        Next
+        Return SelectVersion
+    End Function
+
+#End Region
+
+#Region "OptiFine"
+
+    Public OptiFineState As LoadState = LoadState.Failed '加载状态
+    Public OptiFineArray As New ArrayList '代码分析的数组
+    Public OptiFineInfo As String = "" '获取的代码
+    Public Structure OptiFineVersion
+        Dim id As String
+        Dim url As String
+        Dim time As String
+        Dim getByBMCLAPI As Boolean
+    End Structure
+    Public Sub GetOptiFineBasic()
+
+        '初始化
+        OptiFineState = LoadState.Loading
+        OptiFineArray = New ArrayList
+        OptiFineInfo = ""
+        If MODE_OFFLINE Then
+            OptiFineState = LoadState.NoConnection
+            Exit Sub
+        End If
+
+        '官方源
+
+        '获取版本列表
+        Try
+            log("[DownloadLeft] 获取 OptiFine 版本列表开始")
+            OptiFineInfo = GetWebsiteCode("http://www.optifine.net/downloads", Encoding.Default)
+            If Len(OptiFineInfo) < 200 Then Throw New WebException("获取到的列表长度不足:" & OptiFineInfo)
+        Catch ex As Exception
+            ExShow(ex, "获取 OptiFine 版本列表失败")
+            OptiFineInfo = ""
+            GoTo BMCLAPI
+        End Try
+        log("[DownloadLeft] 获取 OptiFine 版本列表成功")
+
+        '加载版本列表信息
+        Try
+            '预处理
+            Dim ids As ArrayList = RegexSearch(OptiFineInfo, "(?<=downloadLineFile(First)?'>)[^<]*")
+            Dim urls As ArrayList = RegexSearch(OptiFineInfo, "(?<=downloadLineMirror'><a href="")[^""]*")
+            Dim times As ArrayList = RegexSearch(OptiFineInfo, "(?<=downloadLineDate'>)[^<]*")
+            '长度检查
+            If Not (ids.Count = urls.Count And urls.Count = times.Count) Then Throw New WebException("获取到的列表长度不等")
+            '添加
+            For i = 0 To ids.Count - 1
+                OptiFineArray.Add(New OptiFineVersion With {
+                                  .id = ids(i).Replace("OptiFine ", ""),
+                                  .url = urls(i),
+                                  .time = times(i).ToString.Split(".")(2) & "-" & times(i).ToString.Split(".")(1) & "-" & times(i).ToString.Split(".")(0),
+                                  .getByBMCLAPI = False})
+            Next
+            GoTo Success
+        Catch ex As Exception
+            ExShow(ex, "加载 OptiFine 版本列表信息失败")
+            OptiFineInfo = ""
+            GoTo BMCLAPI
+        End Try
+
+        'BMCLAPI 源
+BMCLAPI:
+
+        '获取版本列表
+        Try
+            log("[DownloadLeft] 使用 BMCLAPI 源获取 OptiFine 版本列表开始")
+            OptiFineInfo = GetWebsiteCode("http://bmclapi2.bangbang93.com/optifine/versionList", Encoding.Default)
+            If Len(OptiFineInfo) < 200 Then Throw New WebException("获取到的列表长度不足:" & OptiFineInfo)
+        Catch ex As Exception
+            ExShow(ex, "使用 BMCLAPI 源获取 OptiFine 版本列表失败")
+            OptiFineInfo = ""
+            OptiFineState = LoadState.Failed
+            Exit Sub
+        End Try
+        log("[DownloadLeft] 使用 BMCLAPI 源获取 OptiFine 版本列表成功")
+
+        '加载版本列表信息
+        Try
+            '预处理
+            Dim filename As ArrayList = RegexSearch(OptiFineInfo, "(?<=filename"":"")[^""]*")
+            Dim id As ArrayList = RegexSearch(OptiFineInfo, "(?<=OptiFine_).*?(?=\.jar)")
+            '添加
+            For i = 0 To filename.Count - 1
+                OptiFineArray.Add(New OptiFineVersion With {
+                                  .id = id(i).Replace("_", " "),
+                                  .url = "http://optifine.net/adloadx?f=" & filename(i),
+                                  .time = "",
+                                  .getByBMCLAPI = True
+                                  })
+            Next
+            If selecter.SelectIndexName = "OptiFine" Then ShowHint("OptiFine 官方列表获取失败,列表由 BMCLAPI 提供")
+            GoTo Success
+        Catch ex As Exception
+            ExShow(ex, "加载 OptiFine 版本列表信息失败")
+            OptiFineInfo = ""
+            OptiFineState = LoadState.Failed
+            Exit Sub
+        End Try
+
+Success:
+        log("[DownloadLeft] 加载 OptiFine 版本列表信息成功")
+        OptiFineState = LoadState.Success
+
+    End Sub
+
+    Private Sub OptiFineDownloadStart(ByVal sender As ListItem, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles item1.ButtonClick, item2.ButtonClick
+        If IsNothing(sender.Tag) Or Not selecter.SelectIndexName = "OptiFine" Then Exit Sub
+        '初始化信息
+        Dim ver As OptiFineVersion = sender.Tag
+        If WebGroups.ContainsKey("OptiFine " & ver.id) Then
+            ShowHint(New HintConverter("正在下载中,请勿重复操作", HintState.Warn))
+            Exit Sub
+        End If
+        '获取数据
+        Try
+            sender.ShowButton = False
+            Dim Version As String = Split(ver.id)(0)
+            Dim BMCLAPIAddress As String = ""
+            If ver.id.Contains("pre") Then
+                BMCLAPIAddress = "http://bmclapi2.bangbang93.com/maven/com/optifine/" & ver.id.Split(" ")(0) & "/preview_OptiFine_" & ver.id.Replace(" ", "_").Replace("_pre", "") & ".jar"
+            Else
+                BMCLAPIAddress = "http://bmclapi2.bangbang93.com/maven/com/optifine/" & ver.id.Split(" ")(0) & "/OptiFine_" & ver.id.Replace(" ", "_") & ".jar"
+            End If
+            '下载
+            log("[DownloadLeft] 下载 OptiFine:" & ver.id)
+            ShowHint("OptiFine " & ver.id & " 开始下载")
+            SendStat("下载", "OptiFine", ver.id)
+            Dim th As New Thread(Sub()
+                                     Try
+                                         If ver.getByBMCLAPI Then
+                                             WebStart({New WebRequireFile With {.WebURLs = New ArrayList From {
+                                                          BMCLAPIAddress,
+                                                          BMCLAPIAddress
+                                                      }, .LocalFolder = PATH_DOWNLOAD, .LocalName = "OptiFine_" & ver.id.Replace(" ", "_") & ".jar", .KnownFileSize = 1024 * 64}
+                                                  }, "OptiFine " & ver.id, AddressOf OptiFineDownloadSuccess, AddressOf OptiFineDownloadFail, WebRequireSize.AtLeast)
+                                         Else
+                                             Dim OfficialAddress As String = ""
+                                             Try
+                                                 OfficialAddress = RegexSearch(GetWebsiteCode(ver.url, New UTF8Encoding(False)), "(?<=Download[^\\s\\S]+a href="")[^""]+")(0)
+                                             Catch
+                                             End Try
+                                             If ReadIni("setup", "DownOptiFine", "1") = "0" Then
+                                                 '官方
+                                                 WebStart({New WebRequireFile With {.WebURLs = New ArrayList From {
+                                                              "http://optifine.net/" & OfficialAddress,
+                                                              BMCLAPIAddress,
+                                                              "http://optifine.net/" & OfficialAddress,
+                                                              BMCLAPIAddress
+                                                          }, .LocalFolder = PATH_DOWNLOAD, .LocalName = "OptiFine_" & ver.id.Replace(" ", "_") & ".jar", .KnownFileSize = 1024 * 64}
+                                                      }, "OptiFine " & ver.id, AddressOf OptiFineDownloadSuccess, AddressOf OptiFineDownloadFail, WebRequireSize.AtLeast)
+                                             Else
+                                                 'BMCLAPI
+                                                 WebStart({New WebRequireFile With {.WebURLs = New ArrayList From {
+                                                              BMCLAPIAddress,
+                                                              "http://optifine.net/" & OfficialAddress,
+                                                              BMCLAPIAddress,
+                                                              "http://optifine.net/" & OfficialAddress
+                                                          }, .LocalFolder = PATH_DOWNLOAD, .LocalName = "OptiFine_" & ver.id.Replace(" ", "_") & ".jar", .KnownFileSize = 1024 * 64}
+                                                      }, "OptiFine " & ver.id, AddressOf OptiFineDownloadSuccess, AddressOf OptiFineDownloadFail, WebRequireSize.AtLeast)
+                                             End If
+                                         End If
+                                     Catch ex As Exception
+                                         ExShow(ex, "下载 OptiFine 失败:" & ver.id)
+                                         Application.Current.Dispatcher.Invoke(CType(AddressOf OptiFineDownloadFail, ParameterizedThreadStart), ver.id)
+                                     End Try
+                                     frmMain.Dispatcher.Invoke(Sub() sender.ShowButton = True)
+                                 End Sub)
+            th.Start()
+            '检查引用版本
+            If GetFileSize(PATH_MC & "versions\" & Version & "\" & Version & ".jar") < 1024 Then
+                '引用版本不存在
+                log("[DownloadLeft] OptiFine 对应的引用版本不存在:" & Version)
+                Dim RequireVersion As MinecraftVersion = MinecraftGet(Version)
+                If Not IsNothing(RequireVersion.id) Then MinecraftDownloadStart(RequireVersion)
+            End If
+        Catch ex As Exception
+            ExShow(ex, "处理下载地址时出错", ErrorLevel.MsgboxAndFeedback)
+            Exit Sub
+        End Try
+    End Sub
+    Private Sub OptiFineDownloadSuccess(ByVal Name As String)
+        Dim Version As String = Name.Replace("OptiFine ", "")
+        Try
+            'If ReadIni("setup", "DownOptiFineOpen", "True") Then RunCMD(PATH_DOWNLOAD & "OptiFine_" & Version.Replace(" ", "_") & ".jar", False)
+            Process.Start(PATH_DOWNLOAD)
+            ShowHint(New HintConverter("OptiFine " & Version & " 下载成功", HintState.Finish))
+        Catch ex As Exception
+            ExShow(ex, "OptiFine 下载结束后的处理异常")
+            Try
+                File.Delete(PATH_DOWNLOAD & "OptiFine_" & Version.Replace(" ", "_") & ".jar")
+            Catch : End Try
+            ShowHint(New HintConverter("OptiFine " & Version & " 下载失败:" & GetStringFromException(ex) & "!", HintState.Critical))
+        End Try
+    End Sub
+    Private Sub OptiFineDownloadFail(ByVal Name As String)
+        Dim Version As String = Name.Replace("OptiFine ", "")
+        ShowHint(New HintConverter("OptiFine " & Version & " 下载失败", HintState.Critical))
+    End Sub
+
+#End Region
+
+#Region "ForgeVersion"
+
+    Public ForgeVersionState As LoadState = LoadState.Failed '加载状态
+    Public ForgeVersionArray As New ArrayList '代码分析的数组
+    Public ForgeVersionInfo As String = "" '获取的代码
+    Public Sub GetForgeVersionBasic()
+
+        '初始化
+        ForgeVersionState = LoadState.Loading
+        ForgeVersionArray = New ArrayList
+        ForgeVersionInfo = ""
+        If MODE_OFFLINE Then
+            ForgeVersionState = LoadState.NoConnection
+            Exit Sub
+        End If
+
+        '获取版本列表
+        Try
+            log("[DownloadLeft] 获取Forge可用版本列表开始")
+            ForgeVersionInfo = GetWebsiteCode("http://bmclapi2.bangbang93.com/forge/minecraft", Encoding.Default)
+            If Len(ForgeVersionInfo) < 40 Then Throw New WebException("获取到的列表长度不足:" & ForgeVersionInfo)
+        Catch ex As Exception
+            ExShow(ex, "获取Forge可用版本列表失败")
+            ForgeVersionInfo = ""
+            ForgeVersionState = LoadState.Failed
+            Exit Sub
+        End Try
+        log("[DownloadLeft] 获取Forge可用版本列表成功")
+
+        '加载版本列表信息
+        Try
+            '添加
+            'ForgeVersionArray.AddRange(ForgeVersionInfo.Replace("[", "").Replace("]", "").Replace("""", "").Replace("null", "").Replace(",,", "").Split(",")) 'RegexSearch(ForgeVersionInfo, "1.([7-9]{1}.[0-9]+|[0-9]{2}[0-9.]*)(?="")")
+            ForgeVersionArray = RegexSearch(ForgeVersionInfo, "1.([0-9]{1,2}.[0-9]+|[0-9]{1,2}[0-9.]*)(?="")")
+            '排序
+            Dim CurrentPosition As Integer = 1
+            ForgeVersionArray.Reverse()
+            Do While CurrentPosition <= ForgeVersionArray.Count - 1
+                If CurrentPosition < 1 Then CurrentPosition = 1
+                Dim Smaller() As String = ForgeVersionArray(CurrentPosition).ToString.Split(".")
+                Dim Larger() As String = ForgeVersionArray(CurrentPosition - 1).ToString.Split(".")
+                '比较大小
+                For i = 0 To 2
+                    Select Case Val(If(Larger.Length >= i + 1, Larger(i), "0"))
+                        Case Val(If(Smaller.Length >= i + 1, Smaller(i), "0"))
+                            '相等则不处理,继续比较下一位
+                        Case Is < Val(If(Smaller.Length >= i + 1, Smaller(i), "0"))
+                            '较小
+                            GoTo Swap
+                        Case Else
+                            '较大
+                            GoTo Finish
+                    End Select
+                Next
+                GoTo Finish
+Swap:
+                Dim c As String = ForgeVersionArray(CurrentPosition)
+                ForgeVersionArray(CurrentPosition) = ForgeVersionArray(CurrentPosition - 1)
+                ForgeVersionArray(CurrentPosition - 1) = c
+                CurrentPosition = CurrentPosition - 2
+Finish:
+                CurrentPosition = CurrentPosition + 1
+            Loop
+        Catch ex As Exception
+            ExShow(ex, "加载Forge可用版本列表信息失败")
+            ForgeVersionInfo = ""
+            ForgeVersionState = LoadState.Failed
+            Exit Sub
+        End Try
+
+        log("[DownloadLeft] 加载Forge可用版本列表信息成功")
+        ForgeVersionState = LoadState.Success
+
+    End Sub
+
+    Private Sub ChangeForgeVersionSelection(ByVal sender As ListItem, ByVal e As EventArgs)
+        If Not sender.Checked Then Exit Sub
+        panVersion.Children.Clear()
+        scrollVersion.Visibility = Visibility.Collapsed
+        scrollVersion.Value = 0
+        labRight.Content = "加载中"
+        ForgeCurrent = sender.MainText
+        Dim th = New Thread(AddressOf GetForgeBasic)
+        th.Start(sender.MainText)
+    End Sub
+
+#End Region
+
+#Region "Forge"
+
+    Public ForgeState As LoadState = LoadState.Loading '加载状态
+    Public ForgeArray As New ArrayList '代码分析的数组
+    Public ForgeInfo As String = "" '获取的代码
+    Public ForgeCurrent As String = "" '目前的加载版本
+    Public Structure ForgeVersion
+        Dim version As String
+        Dim build As String
+        Dim time As String
+        Dim branch As String
+    End Structure
+    Public Sub GetForgeBasic(ByVal version As String)
+        Try
+
+            '初始化
+            ForgeState = LoadState.Loading
+            ForgeArray = New ArrayList
+            ForgeInfo = ""
+            If MODE_OFFLINE Then
+                ForgeState = LoadState.NoConnection
+                Exit Sub
+            End If
+
+            '获取版本列表
+            Try
+                log("[DownloadLeft] 获取Forge版本列表开始:" & version)
+                Dim Data = GetWebsiteCode("http://bmclapi2.bangbang93.com/forge/minecraft/" & version, Encoding.Default)
+                If Not ForgeCurrent = version Then Exit Sub
+                ForgeInfo = Data
+                If Len(ForgeInfo) < 400 Then Throw New WebException("获取到的列表长度不足:" & ForgeInfo)
+            Catch ex As Exception
+                ExShow(ex, "获取Forge版本列表失败:" & version)
+                ForgeInfo = ""
+                ForgeState = LoadState.Failed
+                Exit Sub
+            End Try
+            log("[DownloadLeft] 获取Forge版本列表成功")
+
+            '加载版本列表信息
+            Try
+                log("[DownloadLeft] 加载Forge版本列表信息开始")
+                '预处理
+                Dim builds As ArrayList = RegexSearch(ForgeInfo, "(?<=""build"":)[0-9]*")
+                Dim versions As ArrayList = RegexSearch(ForgeInfo, "(?<=""version"":"")[0-9.]*")
+                Dim branchs As ArrayList = RegexSearch(ForgeInfo, "(?<=""branch"":)[^,]*")
+                Dim times As ArrayList = RegexSearch(ForgeInfo, "(?<=modified"":"")[0-9.\-]*")
+                '检查
+                If Not (builds.Count = versions.Count And versions.Count = times.Count And branchs.Count = times.Count) Then Throw New WebException("获取到的列表长度不等")
+                If Not ForgeCurrent = version Then Exit Sub
+                '添加
+                For i = builds.Count - 1 To 0 Step -1
+                    ForgeArray.Add(New ForgeVersion With {.build = builds(i), .version = versions(i), .time = times(i), .branch = branchs(i)})
+                Next
+            Catch ex As Exception
+                ExShow(ex, "加载Forge版本列表信息失败:" & version)
+                ForgeInfo = ""
+                ForgeState = LoadState.Failed
+                Exit Sub
+            End Try
+
+            log("[DownloadLeft] 加载Forge版本列表信息成功")
+            ForgeState = LoadState.Success
+
+        Catch
+        End Try
+    End Sub
+
+    Private Sub ForgeDownloadStart(ByVal sender As ListItem, ByVal e As System.Windows.Input.MouseButtonEventArgs)
+        If IsNothing(sender.Tag) Or Not selecter.SelectIndexName = "Forge" Then Exit Sub
+        '初始化信息
+        Dim ver As ForgeVersion = sender.Tag
+        If WebGroups.ContainsKey("Forge " & ver.version) Then
+            ShowHint(New HintConverter("正在下载中,请勿重复操作", HintState.Warn))
+            Exit Sub
+        End If
+
+        '选择操作
+        If Val(ReadIni("setup", "DownForgeAction", "-1")) = "-1" Then
+            WriteIni("setup", "DownForgeAction", MyMsgbox("在 Forge 下载结束后,是自动安装该版本,还是打开下载文件夹但是不安装?你稍候可以在设置页面中再修改这个设置。" & vbCrLf & "如果你不清楚如何手动安装 Forge,建议选择自动安装。", "选择默认动作", "自动安装", "只打开文件夹") - 1)
+        End If
+
+        '下载
+        log("[DownloadLeft] 下载Forge:" & ver.build)
+        ShowHint("Forge " & ver.version & " 开始下载")
+        SendStat("下载", "Forge", ver.version, ver.build)
+        Dim th As New Thread(Sub()
+                                 Try
+                                     Dim FileName As String = ForgeCurrent & "-" & ver.version & If(ver.branch = "null", "", "-" & ver.branch.Replace("""", ""))
+                                     WebStart({New WebRequireFile With {
+                                                     .WebURLs = New ArrayList From {
+                                                         "http://bmclapi2.bangbang93.com/forge/download/" & ver.build,
+                                                         "http://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" & FileName & "/forge-" & FileName & "-installer.jar",
+                                                         "http://files.minecraftforge.net/maven/net/minecraftforge/forge/" & FileName & "/forge-" & FileName & "-installer.jar",
+                                                         "http://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" & FileName & "/forge-" & FileName & "-universal.zip",
+                                                         "http://files.minecraftforge.net/maven/net/minecraftforge/forge/" & FileName & "/forge-" & FileName & "-universal.zip",
+                                                         "http://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" & FileName & "/forge-" & FileName & "-universal.jar",
+                                                         "http://files.minecraftforge.net/maven/net/minecraftforge/forge/" & FileName & "/forge-" & FileName & "-universal.jar",
+                                                         "http://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" & FileName & "/forge-" & FileName & "-client.zip",
+                                                         "http://files.minecraftforge.net/maven/net/minecraftforge/forge/" & FileName & "/forge-" & FileName & "-client.zip"},
+                                                     .LocalFolder = PATH_DOWNLOAD, .KnownFileSize = 1024 * 16
+                                              }}, "Forge " & ver.version, AddressOf ForgeDownloadSuccess, AddressOf ForgeDownloadFail, WebRequireSize.AtLeast)
+                                 Catch ex As Exception
+                                     ExShow(ex, "下载Forge失败:" & ver.version)
+                                     Application.Current.Dispatcher.Invoke(CType(AddressOf ForgeDownloadFail, ParameterizedThreadStart), ver.version)
+                                 End Try
+                             End Sub)
+        th.Start()
+
+        '检查引用版本
+        If GetFileSize(PATH_MC & "versions\" & ForgeCurrent & "\" & ForgeCurrent & ".jar") < 1024 Then
+            '引用版本不存在
+            log("[DownloadLeft] Forge 对应的引用版本不存在:" & ForgeCurrent)
+            Dim RequireVersion As MinecraftVersion = MinecraftGet(ForgeCurrent)
+            If Not IsNothing(RequireVersion.id) Then MinecraftDownloadStart(RequireVersion)
+        End If
+
+    End Sub
+    Private Sub ForgeDownloadSuccess(ByVal Name As String)
+        Dim Version = Name.Replace("Forge ", "")
+        Try
+            If Val(ReadIni("setup", "DownForgeAction", "1")) = 0 Then
+                '自动安装
+                Try
+                    Dim FoundFile As Boolean = False
+                    For Each File As FileInfo In New DirectoryInfo(PATH_DOWNLOAD).EnumerateFiles
+                        If File.Name.Contains(Version) Then
+                            log("[DownloadLeft] 自动安装目标文件:" & File.FullName)
+                            FoundFile = True
+
+                            If Not File.Name.Contains("installer") Then
+NotSupport:
+                                ShowHint(New HintConverter("Forge " & Version & " 下载成功,但该版本过老,不支持自动安装", HintState.Finish))
+                                Process.Start(PATH_DOWNLOAD)
+                                Exit Sub
+                            End If
+
+                            '自动安装
+                            Dim InstallDir As String = PATH & "PCL\cache\forgeinstall\" & Version
+                            Directory.CreateDirectory(InstallDir)
+                            Using zip As New ZipFile(File.FullName)
+                                zip.ExtractSelectedEntries("install_profile.json", "", InstallDir, ExtractExistingFileAction.OverwriteSilently)
+                                Dim Json As JObject = ReadJson(ReadFileToEnd(InstallDir & "\install_profile.json"))
+
+                                '判断是否为 1.5-1.6 的 Forge
+                                Dim FilePath As String = Json("install")("filePath").ToString
+                                If FilePath.Contains("-1.5") Or FilePath.Contains("-1.6") Then
+                                    Directory.Delete(InstallDir, True)
+                                    GoTo NotSupport
+                                End If
+                                ShowHint(New HintConverter("Forge " & Version & " 下载成功", HintState.Finish))
+
+                                '解压Forge主文件
+                                Dim LibPath As String = GetPathFromFullPath(GetPathFromLibrary(Json("install")("path")))
+                                Directory.CreateDirectory(LibPath)
+                                zip.ExtractSelectedEntries(FilePath, "", LibPath, ExtractExistingFileAction.OverwriteSilently)
+                                IO.File.Delete(GetPathFromLibrary(Json("install")("path")))
+                                FileSystem.Rename(LibPath & FilePath, GetPathFromLibrary(Json("install")("path")))
+
+                                '输出Json
+                                Dim VersionId As String = Json("versionInfo")("id")
+                                Dim VersionPath As String = PATH_MC & "versions\" & VersionId & "\"
+                                Directory.CreateDirectory(VersionPath)
+                                WriteFile(VersionPath & VersionId & ".json", Json("versionInfo").ToString)
+
+                                '删除缓存文件夹
+                                Directory.Delete(InstallDir, True)
+
+                                '刷新版本列表
+                                Dim th As New Thread(AddressOf PoolVersionList)
+                                th.Start()
+
+                            End Using
+                            ShowHint(New HintConverter("Forge " & Version & " 安装成功", HintState.Finish))
+                        End If
+                        If FoundFile Then GoTo FoundFile
+                    Next
+                    ShowHint(New HintConverter("Forge " & Version & " 下载成功,但未找到自动安装文件", HintState.Finish))
+FoundFile:
+                Catch ex As Exception
+                    ExShow(ex, "Forge " & Version & " 安装失败", ErrorLevel.AllUsers)
+                End Try
+            Else
+                '只打开文件夹
+                Process.Start(PATH_DOWNLOAD)
+                ShowHint(New HintConverter("Forge " & Version & " 下载成功", HintState.Finish))
+            End If
+        Catch ex As Exception
+            ExShow(ex, "Forge下载结束后的处理异常:" & Version)
+            Try
+                File.Delete(PATH_DOWNLOAD & "Forge_" & Version & ".jar")
+            Catch : End Try
+            ShowHint(New HintConverter("Forge " & Version & " 下载失败:" & GetStringFromException(ex) & "!", HintState.Critical))
+        End Try
+    End Sub
+    Private Sub ForgeDownloadFail(ByVal Name As String)
+        Dim Version As String = Name.Replace("Forge ", "")
+        ShowHint(New HintConverter("Forge " & Version & " 下载失败", HintState.Critical))
+    End Sub
+
+#End Region
+
+End Class

+ 38 - 0
Plain Craft Launcher/Pages/formDownloadRight.xaml

@@ -0,0 +1,38 @@
+<Window x:Class="formDownloadRight"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:my="clr-namespace:pcl"
+    Title="frmDownload" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignWidth="248" SizeToContent="WidthAndHeight" FontFamily="Microsoft YaHei">
+    <Grid Name="panMain" Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" Height="323" Width="0" HorizontalAlignment="Center" Opacity="0">
+        <Label Content="没有下载任务" Name="labNull" HorizontalAlignment="Center" VerticalAlignment="Top" IsHitTestVisible="False" Margin="0,146,3,0" FontSize="19" Foreground="{DynamicResource Color3}" Opacity="0" />
+        <Grid Margin="5,9,0,0" Name="panBack" Background="#01FFFFFF" Height="305" VerticalAlignment="Top" HorizontalAlignment="Left" Width="203">
+            <StackPanel Orientation="Vertical" UseLayoutRounding="True" Name="panShow" VerticalAlignment="Center">
+<!--
+                <Grid Width="198" Margin="0,5,0,10" Tag="Minecraft 15w32a">
+                    <Path Data="F1 M 38,19C 47.3888,19 55,21.0147 55,23.5038L 55,25.5C 55,27.9853 47.3888,30 38,30C 28.6112,30 21,27.9853 21,25.5L 21,23.5C 21,21.0147 28.6112,19 38,19 Z M 55,52.5C 55,54.9853 47.3888,57 38,57C 28.6112,57 21,54.9853 21,52.5L 21,46.5C 21,48.9853 28.6112,51 38,51C 47.384,51 54.9921,48.9874 55,46.5039L 55,52.5 Z M 55,43.5C 55,45.9853 47.3888,48 38,48C 28.6112,48 21,45.9853 21,43.5L 21,37.5C 21,39.9853 28.6112,42 38,42C 47.384,42 54.9921,39.9874 55,37.5038L 55,43.5 Z M 55,34.5C 55,36.9853 47.3888,39 38,39C 28.6112,39 21,36.9853 21,34.5L 21,28.5C 21,30.9853 28.6112,33 38,33C 47.384,33 54.9921,30.9874 55,28.5038L 55,34.5 Z " Fill="{DynamicResource Color4}" Height="19" Margin="9,9,0,0" Stretch="Fill" Width="17" HorizontalAlignment="Left" VerticalAlignment="Top" UseLayoutRounding="False" />
+                    <Label Content="Minecraft 15w32a" FontSize="16" Foreground="{DynamicResource Color4}" VerticalContentAlignment="Center" Margin="35,0,0,0" Height="36" VerticalAlignment="Top" Padding="0" UseLayoutRounding="False" />
+                    <TextBlock Height="2" VerticalAlignment="Top" Margin="5,37,0,0" Background="{DynamicResource Color2}" HorizontalAlignment="Left" Width="188" />
+                    <TextBlock Height="2" VerticalAlignment="Top" Margin="5,37,0,0" Background="{DynamicResource Color4}" HorizontalAlignment="Left" Width="91" />
+                    <StackPanel HorizontalAlignment="Left" Margin="5,45,0,0" VerticalAlignment="Top" Width="188">
+                        <Grid Height="22">
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="116*" />
+                                <ColumnDefinition Width="Auto" />
+                            </Grid.ColumnDefinitions>
+                            <Image Height="14" HorizontalAlignment="Left" Margin="4,4,0,0" VerticalAlignment="Top" Width="14" Source="/pcl;component/Images/Stats-Finish.png" />
+                            <Label Margin="23,0,0,0" Padding="0,0,3,0" VerticalContentAlignment="Center" Foreground="#FF4B4B4B" FontSize="12" Content="ffffffffffffffffffffffffffffffffffffffffffffff" />
+                            <Label Content="下载成功" FontSize="12" Foreground="#FF4B4B4B" HorizontalAlignment="Right" Padding="0" VerticalContentAlignment="Center" Margin="0,0,3,0" Opacity="0.6" MaxWidth="50" Grid.Column="1" />
+                        </Grid>
+                        <Grid Height="22">
+                            <Image Height="14" HorizontalAlignment="Left" Margin="4,4,0,0" VerticalAlignment="Top" Width="14" Source="/pcl;component/Images/Stats-Running.png" />
+                            <Label Content="15w32a.jar" HorizontalAlignment="Left" Margin="23,0,0,0" Padding="0" VerticalContentAlignment="Center" Foreground="#FF4B4B4B" FontSize="12" MaxWidth="110" />
+                            <Label Content="52%" FontSize="12" Foreground="#FF4B4B4B" HorizontalAlignment="Right" Padding="0" VerticalContentAlignment="Center" Margin="0,0,3,0" Opacity="0.6" MaxWidth="50" />
+                        </Grid>
+                    </StackPanel>
+                </Grid>
+-->
+            </StackPanel>
+        </Grid>
+        <my:VScroll HorizontalAlignment="Right" Margin="0,9,6,9" x:Name="scroll" Opacity="0" IsHitTestVisible="False" />
+    </Grid>
+</Window>

+ 304 - 0
Plain Craft Launcher/Pages/formDownloadRight.xaml.vb

@@ -0,0 +1,304 @@
+Public Class formDownloadRight
+
+    '初始化滚动条
+    Private Sub panShow_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles panShow.Loaded
+        If scroll.SetControl(panShow, True) Then AddHandler panBack.MouseWheel, AddressOf scroll.RunMouseWheel
+        RefreshPage()
+    End Sub
+
+    Private ControlDict As New Dictionary(Of String, Object)
+
+    ''' <summary>
+    ''' 添加一个文件组到 UI。
+    ''' </summary>
+    ''' <param name="Group"></param>
+    Public Sub AddGroup(Group As WebGroup)
+        frmMain.Dispatcher.Invoke(Sub()
+
+
+                                      panShow.UpdateLayout()
+                                      Dim NewGrid As New Grid With {.Width = 203, .Margin = New Thickness(0, 5, 0, 9)}
+                                      ControlDict.Add("MainGrid" & Group.UUID, NewGrid)
+
+                                      Dim NewPath As New Shapes.Path With {.Data = New GeometryConverter().ConvertFromString("F1 M 38,19C 47.3888,19 55,21.0147 55,23.5038L 55,25.5C 55,27.9853 47.3888,30 38,30C 28.6112,30 21,27.9853 21,25.5L 21,23.5C 21,21.0147 28.6112,19 38,19 Z M 55,52.5C 55,54.9853 47.3888,57 38,57C 28.6112,57 21,54.9853 21,52.5L 21,46.5C 21,48.9853 28.6112,51 38,51C 47.384,51 54.9921,48.9874 55,46.5039L 55,52.5 Z M 55,43.5C 55,45.9853 47.3888,48 38,48C 28.6112,48 21,45.9853 21,43.5L 21,37.5C 21,39.9853 28.6112,42 38,42C 47.384,42 54.9921,39.9874 55,37.5038L 55,43.5 Z M 55,34.5C 55,36.9853 47.3888,39 38,39C 28.6112,39 21,36.9853 21,34.5L 21,28.5C 21,30.9853 28.6112,33 38,33C 47.384,33 54.9921,30.9874 55,28.5038L 55,34.5 Z"), .Height = 19, .Stretch = Stretch.Fill, .Width = 17, .HorizontalAlignment = HorizontalAlignment.Left, .VerticalAlignment = VerticalAlignment.Top, .UseLayoutRounding = False, .Margin = New Thickness(9, 9, 0, 0)}
+                                      NewPath.SetResourceReference(Shape.FillProperty, "Color4")
+                                      NewGrid.Children.Add(NewPath)
+
+                                      Dim NewTitle As New Label With {.Content = Group.Name, .FontSize = 16, .VerticalContentAlignment = VerticalAlignment.Center, .Margin = New Thickness(35, 0, 0, 0), .Height = 36, .VerticalAlignment = VerticalAlignment.Top, .Padding = New Thickness(0), .UseLayoutRounding = False, .MaxWidth = 160, .HorizontalAlignment = HorizontalAlignment.Left}
+                                      NewTitle.SetResourceReference(ForegroundProperty, "Color4")
+                                      AddHandler NewTitle.MouseEnter, Sub()
+                                                                          NewTitle.ToolTip = If(NewTitle.ActualWidth > 159, Group.Name, Nothing)
+                                                                      End Sub
+                                      NewGrid.Children.Add(NewTitle)
+
+                                      Dim NewProgress1 As New TextBlock With {.Height = 2, .VerticalAlignment = VerticalAlignment.Top, .Margin = New Thickness(5, 37, 0, 0), .HorizontalAlignment = HorizontalAlignment.Left, .Width = 193, .UseLayoutRounding = True}
+                                      NewProgress1.SetResourceReference(TextBlock.BackgroundProperty, "Color2")
+                                      NewGrid.Children.Add(NewProgress1)
+
+                                      Dim NewProgress2 As New TextBlock With {.Height = 2, .VerticalAlignment = VerticalAlignment.Top, .Margin = New Thickness(5, 37, 0, 0), .HorizontalAlignment = HorizontalAlignment.Left, .Width = 0, .UseLayoutRounding = True, .MaxWidth = 193}
+                                      NewProgress2.SetResourceReference(TextBlock.BackgroundProperty, "Color4")
+                                      NewGrid.Children.Add(NewProgress2)
+                                      ControlDict.Add("Progress" & Group.UUID, NewProgress2)
+
+                                      Dim NewHost As New StackPanel With {.HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(5, 45, 0, 0), .VerticalAlignment = VerticalAlignment.Top, .Width = 193}
+                                      ControlDict.Add("Host" & Group.UUID, NewHost)
+
+                                      NewGrid.Children.Add(NewHost)
+                                      panShow.Children.Add(NewGrid)
+
+                                      AddFileList(Group.Files, NewHost)
+                                      RefreshGroup(Group)
+                                      RefreshPage()
+
+
+                                  End Sub)
+    End Sub
+
+    ''' <summary>
+    ''' 刷新一个 WebGroup 在 UI 中的显示。
+    ''' </summary>
+    ''' <param name="Group"></param>
+    Public Sub RefreshGroup(Group As WebGroup)
+        frmMain.Dispatcher.Invoke(Sub()
+
+
+                                      '刷新进度条
+                                      AniStart({
+                                               AaWidth(ControlDict("Progress" & Group.UUID), 193 * Group.Percent - ControlDict("Progress" & Group.UUID).Width, 250, , New AniEaseJumpEnd(0.6))
+                                           }, "Progress" & Group.UUID)
+
+
+                                  End Sub)
+    End Sub
+
+    ''' <summary>
+    ''' 添加一堆文件到 UI 中的 StackPanel。
+    ''' </summary>
+    Private Sub AddFileList(Files As ArrayList, Host As StackPanel)
+        For Each File As WebFile In Files
+
+            Dim f As WebFile = File
+            Dim NewFileGrid As New Grid With {.Height = 22, .UseLayoutRounding = True, .Background = New MyColor(1, 255, 255, 255), .Width = 193, .HorizontalAlignment = HorizontalAlignment.Left}
+            NewFileGrid.ColumnDefinitions.Add(New ColumnDefinition With {.Width = (New GridLengthConverter).ConvertFromString("1*")})
+            NewFileGrid.ColumnDefinitions.Add(New ColumnDefinition With {.Width = GridLength.Auto})
+            AddHandler NewFileGrid.MouseEnter, Sub()
+                                                   NewFileGrid.ToolTip = "下载地址:" & f.WebAddress &
+                                                                            If(f.WebAddress.Contains("minecraft.net/") Or f.WebAddress.Contains("mojang.com/"), vbCrLf & "下载来源:Mojang 官方", "") &
+                                                                            If(f.WebAddress.Contains("optifine.net/"), vbCrLf & "下载来源:OptiFine 官方", "") &
+                                                                            If(f.WebAddress.Contains("minecraftforge.net/"), vbCrLf & "下载来源:Forge 官方", "") &
+                                                                            If(f.WebAddress.Contains("bangbang93.com/"), vbCrLf & "下载来源:BMCLAPI by bangbang93", "") &
+                                                                            If(f.WebAddress.Contains(TX_SREVER_1) Or f.WebAddress.Contains(TX_SREVER_2) Or f.WebAddress.Contains("bkt.clouddn.com/"), vbCrLf & "下载来源:PCL 服务器", "") &
+                                                                            If(f.State = WebDownloadStats.Download And Not f.GetServerFileSize = WebRequireSize.DontNeed,
+                                                                                  vbCrLf & "文件大小:" & f.GetSize.ToString & " / " & f.TotalSize.ToString &
+                                                                                  vbCrLf & "下载速度:" & f.Speed.ToString & "/s",
+                                                                              "")
+                                               End Sub
+            ControlDict.Add("FileGrid" & File.UUID, NewFileGrid)
+
+            Dim FileImage As New Image With {.Height = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(4, 4, 0, 0), .VerticalAlignment = VerticalAlignment.Top, .Width = 14, .UseLayoutRounding = True, .Tag = "", .Opacity = 0.8}
+            NewFileGrid.Children.Add(FileImage)
+            ControlDict.Add("Image" & File.UUID, FileImage)
+
+            Dim LeftLabel As New Label With {.Margin = New Thickness(23, 0, 0, 0), .Padding = New Thickness(0, 0, 4, 0), .VerticalContentAlignment = VerticalAlignment.Center, .Foreground = New MyColor(255, 75, 75, 75), .FontSize = 12}
+            NewFileGrid.Children.Add(LeftLabel)
+            ControlDict.Add("FileName" & File.UUID, LeftLabel)
+
+            Dim RightLabel As New Label With {.Name = "Label" & File.UUID, .FontSize = 12, .Foreground = New MyColor(255, 75, 75, 75), .HorizontalAlignment = HorizontalAlignment.Right, .Padding = New Thickness(0), .VerticalContentAlignment = VerticalAlignment.Center, .Margin = New Thickness(0, 0, 3, 0), .Opacity = 0.6}
+            NewFileGrid.Children.Add(RightLabel)
+            ControlDict.Add("Label" & File.UUID, RightLabel)
+            Grid.SetColumn(RightLabel, 1)
+
+            Host.Children.Add(NewFileGrid)
+            RefreshFile(File)
+
+
+        Next
+    End Sub
+
+    ''' <summary>
+    ''' 刷新一个 WebFile 在 UI 中的显示。
+    ''' </summary>
+    Public Sub RefreshFile(File As WebFile)
+        frmMain.Dispatcher.Invoke(Sub()
+                                      Try
+
+                                          '查找控件
+                                          Dim FileGrid As Grid = ControlDict("FileGrid" & File.UUID)
+                                          Dim FileImage As Image = ControlDict("Image" & File.UUID)
+                                          Dim FileName As Label = ControlDict("FileName" & File.UUID)
+                                          Dim FileLabel As Label = ControlDict("Label" & File.UUID)
+
+                                          '刷新 Grid 的指向信息
+                                          If FileGrid.IsMouseOver Then
+                                              FileGrid.ToolTip = "下载地址:" & File.WebAddress &
+                                                                            If(File.WebAddress.Contains("minecraft.net/") Or File.WebAddress.Contains("mojang.com/"), vbCrLf & "下载来源:Mojang 官方", "") &
+                                                                            If(File.WebAddress.Contains("optifine.net/"), vbCrLf & "下载来源:OptiFine 官方", "") &
+                                                                            If(File.WebAddress.Contains("minecraftforge.net/"), vbCrLf & "下载来源:Forge 官方", "") &
+                                                                            If(File.WebAddress.Contains("bangbang93.com/"), vbCrLf & "下载来源:BMCLAPI by bangbang93", "") &
+                                                                            If(File.WebAddress.Contains(TX_SREVER_1) Or File.WebAddress.Contains(TX_SREVER_2) Or File.WebAddress.Contains("bkt.clouddn.com/"), vbCrLf & "下载来源:PCL 服务器", "") &
+                                                                            If(File.State = WebDownloadStats.Download And Not File.GetServerFileSize = WebRequireSize.DontNeed,
+                                                                                  vbCrLf & "文件大小:" & File.GetSize.ToString & " / " & File.TotalSize.ToString &
+                                                                                  vbCrLf & "下载速度:" & File.Speed.ToString & "/s",
+                                                                              "")
+                                          End If
+
+                                          '刷新图标
+                                          Dim PictureSource As String = ""
+                                          Select Case File.State
+                                              Case WebDownloadStats.Success
+                                                  PictureSource = PATH_IMAGE & "Stats-Finish.png"
+                                              Case WebDownloadStats.Download, WebDownloadStats.GetSize
+                                                  PictureSource = PATH_IMAGE & "Stats-Running.png"
+                                              Case WebDownloadStats.Fail
+                                                  PictureSource = PATH_IMAGE & "Stats-Fail.png"
+                                              Case WebDownloadStats.Wait, WebDownloadStats.FirstLoad
+                                                  PictureSource = PATH_IMAGE & "Stats-Wait.png"
+                                              Case Else
+                                                  PictureSource = PATH_IMAGE & "Stats-Retry.png"
+                                          End Select
+                                          If Not FileImage.Tag = PictureSource Then
+                                              FileImage.Tag = PictureSource
+                                              FileImage.Source = New BitmapImage(New Uri(PictureSource, UriKind.Absolute))
+                                          End If
+
+                                          '刷新右边的文本
+                                          Dim ShouldText As String
+                                          Select Case File.State
+                                              Case WebDownloadStats.Download
+                                                  ShouldText = If(File.TotalSize > 0, File.Percent * 100 & "%", "")
+                                              Case WebDownloadStats.Fail
+                                                  ShouldText = "下载失败"
+                                              Case WebDownloadStats.Wait, WebDownloadStats.FirstLoad
+                                                  ShouldText = ""
+                                              Case WebDownloadStats.GetSize
+                                                  ShouldText = "准备中"
+                                              Case WebDownloadStats.Success
+                                                  ShouldText = "下载成功"
+                                              Case Else
+                                                  ShouldText = "等待重试"
+                                          End Select
+                                          If Not ShouldText = FileLabel.Content Then
+                                              FileLabel.Content = ShouldText
+                                              '移除下载成功的文件
+                                              If ShouldText = "下载成功" Then
+                                                  RemoveFile(File)
+                                              End If
+                                          End If
+
+                                          '刷新文件名
+                                          If Not FileName.Content = File.LocalName Then FileName.Content = File.LocalName
+
+                                      Catch
+                                      End Try
+                                  End Sub)
+    End Sub
+
+    ''' <summary>
+    ''' 从 UI 中删除一个文件组。
+    ''' </summary>
+    Public Sub RemoveGroup(Group As WebGroup)
+        frmMain.Dispatcher.Invoke(Sub()
+
+
+                                      Dim Target As Grid = ControlDict("MainGrid" & Group.UUID)
+                                      AniStart({
+                                                   AaOpacity(Target, -1, 250),
+                                                   AaHeight(Target, -Target.ActualHeight, 250, , New AniEaseEnd),
+                                                   AaCode({"Remove", panShow.Children, Target}, , True)
+                                               }, "Remove" & GetUUID())
+                                      Target.Height = Target.ActualHeight
+                                      RefreshPage()
+
+
+                                  End Sub)
+    End Sub
+
+    ''' <summary>
+    ''' 从 UI 中删除一个文件。
+    ''' </summary>
+    Public Sub RemoveFile(File As WebFile)
+        frmMain.Dispatcher.Invoke(Sub()
+                                      Try
+
+
+                                          If Not WebGroups.Keys.Contains(File.GroupName) Then Exit Sub
+                                          Dim FileGrid As Grid = ControlDict("FileGrid" & File.UUID)
+                                          Dim Stack As StackPanel = ControlDict("Host" & WebGroups(File.GroupName).UUID)
+                                          If Stack.Children.Count > 30 Then
+                                              AniStart({
+                                                  AaCode({"Remove", Stack.Children, FileGrid}, 700)
+                                               }, "File" & File.UUID, False)
+                                          Else
+                                              AniStart({
+                                                  AaX(FileGrid, -191, 200, 600, New AniEaseJumpStart(0.25)),
+                                                  AaOpacity(FileGrid, -FileGrid.Opacity, 150, 700),
+                                                  AaHeight(FileGrid, -22, 100, 750),
+                                                  AaCode({"Remove", Stack.Children, FileGrid}, 850)
+                                               }, "File" & File.UUID, False)
+                                          End If
+
+
+                                      Catch
+                                      End Try
+                                  End Sub)
+    End Sub
+
+    Private LastPageVisible As Boolean = True
+    ''' <summary>
+    ''' 刷新下载进度页可见性。
+    ''' </summary>
+    Private Sub RefreshPage()
+
+        If WebGroups.Count = 0 Then
+            '没有下载任务
+            If scroll.IsHitTestVisible = True Or (LastPageVisible Xor frmDownloadLeft.panMain.Visibility = Visibility.Visible) Then
+                LastPageVisible = frmDownloadLeft.panMain.Visibility = Visibility.Visible
+                scroll.IsHitTestVisible = False
+                If frmDownloadLeft.panMain.Visibility = Visibility.Collapsed Then
+                    '下载页被隐藏
+                    panMain.Width = 230
+                    panMain.Opacity = 1
+                    AniStart({
+                                 AaOpacity(panBack, -panBack.Opacity, 200),
+                                 AaOpacity(scroll, -scroll.Opacity, 200),
+                                 AaOpacity(labNull, 0.9 - labNull.Opacity, 200),
+                                 AaY(labNull, 146 - labNull.Margin.Top, 200, , New AniEaseEnd)
+                             }, "DownloadNothing")
+                Else
+                    '下载页未被隐藏
+                    AniStart({
+                                 AaWidth(panMain, -panMain.Width, 200, , New AniEaseEnd),
+                                 AaOpacity(panMain, -panMain.Opacity, 150),
+                                 AaWidth(frmDownloadLeft.panMain, 227 + 504 - frmDownloadLeft.panMain.Width, 250, , New AniEaseEnd)
+                             }, "DownloadNothing")
+                End If
+            End If
+        Else
+            '有下载任务
+            If scroll.IsHitTestVisible = False Or (LastPageVisible Xor frmDownloadLeft.panMain.Visibility = Visibility.Visible) Then
+                LastPageVisible = frmDownloadLeft.panMain.Visibility = Visibility.Visible
+                scroll.IsHitTestVisible = True
+                If frmDownloadLeft.panMain.Visibility = Visibility.Collapsed Then
+                    '下载页被隐藏
+                    panMain.Width = 230
+                    panMain.Opacity = 1
+                    AniStart({
+                                 AaOpacity(panBack, 1 - panBack.Opacity, 200),
+                                 AaOpacity(labNull, -labNull.Opacity, 200),
+                                 AaY(labNull, 152 - labNull.Margin.Top, 200, , New AniEaseStart)
+                             }, "DownloadNothing")
+                Else
+                    '下载页未被隐藏
+                    AniStart({
+                                 AaWidth(panMain, 230 - panMain.Width, 200, , New AniEaseEnd),
+                                 AaOpacity(panMain, 1 - panMain.Opacity, 150, 50),
+                                 AaWidth(frmDownloadLeft.panMain, 504 - frmDownloadLeft.panMain.Width, 250, , New AniEaseEnd)
+                             }, "DownloadNothing")
+                End If
+            End If
+        End If
+
+
+    End Sub
+
+End Class

+ 81 - 0
Plain Craft Launcher/Pages/formGuild.xaml

@@ -0,0 +1,81 @@
+<Window
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:my="clr-namespace:pcl"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="formGuild"
+    Title="frmGuild" FontFamily="Microsoft YaHei" Width="748" Height="406">
+    <Grid x:Name="panMain" Height="375" Width="740" Background="White">
+
+        <Grid x:Name="panLoad" HorizontalAlignment="Left" Height="375" VerticalAlignment="Top" Width="740" RenderTransformOrigin="0.5,0.5" d:IsHidden="True">
+            <Grid.RenderTransform>
+                <ScaleTransform/>
+            </Grid.RenderTransform>
+            <my:Loading HorizontalAlignment="Center" VerticalAlignment="Top" Margin="345,133,345,0" Height="50" Width="50" BorderBrush="{DynamicResource Color4}"/>
+            <Label x:Name="labLoad" Content="初始化中" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,195,0,0" FontSize="17" Foreground="{DynamicResource Color4}"/>
+        </Grid>
+
+        <Grid x:Name="panJava" HorizontalAlignment="Left" Height="375" VerticalAlignment="Top" Width="740" RenderTransformOrigin="0.5,0.5" d:IsHidden="True">
+            <Grid.RenderTransform>
+                <ScaleTransform/>
+            </Grid.RenderTransform>
+            <Label Content="未找到可用的 Java" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="48,40,0,0" FontSize="26" Foreground="{DynamicResource Color4}"/>
+            <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" Margin="53,100,0,0" FontSize="15" Foreground="#FF3C3C3C" LineHeight="24">
+                Minecraft 需要 Java 才能运行。在开始游戏前,你首先需要下载安装 Java。<LineBreak/>如果你已经安装了 Java,请点击“手动选择”进行查找。
+            </TextBlock>
+            <my:Button x:Name="btnJava" HorizontalAlignment="Left" Height="48" Margin="561,288,0,0" VerticalAlignment="Top" Width="137" Text="下载 Java" FontSize="15"/>
+            <my:Button x:Name="btnJavaSelect" HorizontalAlignment="Left" Height="48" Margin="404,288,0,0" VerticalAlignment="Top" Width="137" Text="手动选择" FontSize="15"/>
+        </Grid>
+
+        <Grid x:Name="panTheme" HorizontalAlignment="Left" Height="375" VerticalAlignment="Top" Width="740" RenderTransformOrigin="0.5,0.5" d:IsHidden="True">
+            <Grid.RenderTransform>
+                <ScaleTransform/>
+            </Grid.RenderTransform>
+            <Label Content="选择主题配色" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="48,40,0,0" FontSize="26" Foreground="{DynamicResource Color4}"/>
+            <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" Margin="53,100,0,0" FontSize="15" Foreground="#FF3C3C3C" LineHeight="24">
+                选择一个你喜欢的主题配色。<LineBreak/>稍后你可以在设置页面更改这些设置,或者自定义自己想要的主题配色。
+            </TextBlock>
+            <my:Button x:Name="btnTheme" HorizontalAlignment="Left" Height="48" Margin="561,288,0,0" VerticalAlignment="Top" Width="137" Text="确定" FontSize="15"/>
+            <Rectangle x:Name="rectTheme0" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="40" Margin="54,166,0,0" RadiusX="1" RadiusY="1" Stroke="#FF0051AA" Tag="0" Fill="#FF0079FF"/>
+            <Rectangle x:Name="rectTheme1" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="40" Margin="115,166,0,0" RadiusX="1" RadiusY="1" Stroke="#FF5B5B5B" Tag="1" Fill="#FF7F7F7F"/>
+            <Rectangle x:Name="rectTheme2" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="40" Margin="176,166,0,0" RadiusX="1" RadiusY="1" Stroke="#FFBF5600" Tag="2" Fill="#FFFF7E15"/>
+            <Rectangle x:Name="rectTheme3" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="40" Margin="237,166,0,0" RadiusX="1" RadiusY="1" Stroke="#FF1D8D28" Tag="3" Fill="#FF2BD53C"/>
+        </Grid>
+
+        <Grid x:Name="panPlayer" HorizontalAlignment="Left" Height="375" VerticalAlignment="Top" Width="740" RenderTransformOrigin="0.5,0.5" d:IsHidden="True">
+            <Grid.RenderTransform>
+                <ScaleTransform/>
+            </Grid.RenderTransform>
+            <Label Content="选择玩家种类" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="48,40,0,0" FontSize="26" Foreground="{DynamicResource Color4}"/>
+            <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" Margin="53,100,0,0" FontSize="15" Foreground="#FF3C3C3C" LineHeight="24">
+                你是刚接触 Minecraft 不久的新玩家,还是一位熟知 Minecraft 的老玩家?<LineBreak/>PCL 将会为新玩家自动选择较为合适的配置,为老玩家提供更充分的自定义空间。
+            </TextBlock>
+            <my:Button x:Name="btnPlayerNew" HorizontalAlignment="Left" Height="48" Margin="561,288,0,0" VerticalAlignment="Top" Width="137" Text="我是新玩家" FontSize="15"/>
+            <my:Button x:Name="btnPlayerOld" HorizontalAlignment="Left" Height="48" Margin="404,288,0,0" VerticalAlignment="Top" Width="137" Text="我是老玩家" FontSize="15"/>
+        </Grid>
+
+        <Grid x:Name="panMinecraft" HorizontalAlignment="Left" Height="375" VerticalAlignment="Top" Width="740" RenderTransformOrigin="0.5,0.5" d:IsHidden="True">
+            <Grid.RenderTransform>
+                <ScaleTransform/>
+            </Grid.RenderTransform>
+            <Label Content="Minecraft 文件夹确认" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="48,40,0,0" FontSize="26" Foreground="{DynamicResource Color4}"/>
+            <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" Margin="53,100,0,0" FontSize="15" Foreground="#FF3C3C3C" LineHeight="24">
+                是使用官方启动器的 Minecraft 文件夹,还是在当前目录下创建一个新的文件夹?<LineBreak/>稍后你可以在设置页面更改这个设置,或者添加其它的 Minecraft 文件夹。
+            </TextBlock>
+            <my:Button x:Name="btnMinecraftMojang" HorizontalAlignment="Left" Height="48" Margin="561,288,0,0" VerticalAlignment="Top" Width="137" Text="使用官启文件夹" FontSize="15"/>
+            <my:Button x:Name="btnMinecraftThere" HorizontalAlignment="Left" Height="48" Margin="404,288,0,0" VerticalAlignment="Top" Width="137" Text="创建新文件夹" FontSize="15"/>
+        </Grid>
+        <Grid x:Name="panVersion" HorizontalAlignment="Left" Height="375" VerticalAlignment="Top" Width="740" RenderTransformOrigin="0.5,0.5" d:IsHidden="True">
+            <Grid.RenderTransform>
+                <ScaleTransform/>
+            </Grid.RenderTransform>
+            <Label Content="版本隔离设置" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="48,40,0,0" FontSize="26" Foreground="{DynamicResource Color4}"/>
+            <TextBlock x:Name="labVersion" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="53,100,0,0" FontSize="15" Foreground="#FF3C3C3C" LineHeight="24" TextWrapping="Wrap" Width="636">
+                请选择你希望的版本隔离模式。<LineBreak/>进行版本隔离后,版本的 MOD、存档、资源包等将分别保存在该版本的版本文件夹中,各个版本互不干涉(各个版本文件夹位置为 .minecraft/versions/版本名/ )。这在使用多个版本的 Forge 时可以有效防止 MOD 版本冲突,但也会因为存档、设置等无法跨版本带来一些麻烦。<LineBreak/><LineBreak/>如果不清楚应该选什么,请选择“不隔离”。
+            </TextBlock>
+            <my:Button x:Name="labVersionAll" HorizontalAlignment="Left" Height="48" Margin="561,288,0,0" VerticalAlignment="Top" Width="137" Text="隔离全部版本" FontSize="15"/>
+            <my:Button x:Name="labVersionForge" HorizontalAlignment="Left" Height="48" Margin="404,288,0,0" VerticalAlignment="Top" Width="137" Text="仅隔离 Forge" FontSize="15"/>
+            <my:Button x:Name="labVersionNo" HorizontalAlignment="Left" Height="48" Margin="245,288,0,0" VerticalAlignment="Top" Width="137" Text="不隔离" FontSize="15"/>
+        </Grid>
+
+    </Grid>
+</Window>

+ 254 - 0
Plain Craft Launcher/Pages/formGuild.xaml.vb

@@ -0,0 +1,254 @@
+Public Class formGuild
+
+    '切换页面
+    Private Sub ShowPage(Page As Grid)
+        frmMain.Dispatcher.Invoke(Sub()
+
+                                      log("[Guild] 显示页面:" & Page.Name)
+                                      CType(Page.RenderTransform, ScaleTransform).ScaleX = 0.96
+                                      CType(Page.RenderTransform, ScaleTransform).ScaleY = 0.96
+                                      Page.Opacity = 0
+                                      Page.Visibility = Visibility.Visible
+                                      AniStart({
+                                                    AaScaleTransform(Page, 1 - CType(Page.RenderTransform, ScaleTransform).ScaleX, 200, 130, New AniEaseJumpEnd(0.8)),
+                                                    AaOpacity(Page, 1 - Page.Opacity, 150, 130)
+                                               }, "GuildChangePage" & GetUUID())
+
+                                  End Sub)
+    End Sub
+    Private Sub HidePage(Page As Grid)
+        frmMain.Dispatcher.Invoke(Sub()
+
+                                      log("[Guild] 隐藏页面:" & Page.Name)
+                                      AniStart({
+                                                    AaScaleTransform(Page, 0.96 - CType(Page.RenderTransform, ScaleTransform).ScaleX, 200,, New AniEaseEnd),
+                                                    AaOpacity(Page, -Page.Opacity, 150),
+                                                    AaCode({"Visible", Page, False}, 200)
+                                               }, "GuildChangePage" & GetUUID())
+
+                                  End Sub)
+    End Sub
+
+    '检测启动器更新与 Java
+    Private Sub formGuild_Loaded(sender As Object, e As RoutedEventArgs) Handles panMain.Loaded
+        panJava.Visibility = Visibility.Collapsed
+        panPlayer.Visibility = Visibility.Collapsed
+        panTheme.Visibility = Visibility.Collapsed
+        panMinecraft.Visibility = Visibility.Collapsed
+        panVersion.Visibility = Visibility.Collapsed
+        ShowPage(panLoad)
+        Dim th As New Thread(Sub()
+
+                                 '启动器更新
+
+                                 frmMain.Dispatcher.Invoke(Sub() labLoad.Content = "检查启动器更新中")
+                                 Do While NeedUpdate = LoadState.Loading Or NeedUpdate = LoadState.Waiting
+                                     Thread.Sleep(10)
+                                 Loop
+                                 If NeedUpdate = LoadState.Loaded Then
+                                     '需要更新
+                                     frmMain.Dispatcher.Invoke(Sub() labLoad.Content = "启动器更新中")
+                                     WriteIni("setup", "LastVersionCode", "0")
+                                     Dim th2 As New Thread(AddressOf DownloadUpdate)
+                                     th2.Start()
+                                     Do While UpdateState = LoadState.Loading Or UpdateState = LoadState.Waiting
+                                         Thread.Sleep(20)
+                                     Loop
+                                 End If
+
+                                 'Java 检测
+
+                                 frmMain.Dispatcher.Invoke(Sub() labLoad.Content = "检测 Java 中")
+                                 PoolJavaFolder(True)
+                                 If PATH_JAVA = "" Then
+                                     '未找到 Java
+                                     HidePage(panLoad)
+                                     ShowPage(panJava)
+                                     Exit Sub
+                                 End If
+
+                                 frmMain.Dispatcher.Invoke(Sub() labLoad.Content = "准备中")
+                                 HidePage(panLoad)
+                                 ShowPage(panTheme)
+
+                             End Sub)
+        th.Start()
+    End Sub
+
+    'Java 页面
+    Private Sub btnJavaClick(sender As Object, e As RoutedEventArgs) Handles btnJava.Click
+        Process.Start("https://www.java.com/winoffline_installer/")
+        MyMsgbox("请在下载、安装结束后重启电脑。", "提示")
+    End Sub
+    Private Sub btnJavaSelectClick(sender As Object, e As RoutedEventArgs) Handles btnJavaSelect.Click
+        Using fileDialog As New Forms.OpenFileDialog
+            fileDialog.AddExtension = True
+            fileDialog.AutoUpgradeEnabled = True
+            fileDialog.CheckFileExists = True
+            fileDialog.Filter = "JavaW|javaw.exe"
+            fileDialog.Multiselect = False
+            fileDialog.Title = "选择 javaw.exe"
+            fileDialog.ShowDialog()
+            '选择文件结束
+            If Not fileDialog.FileName = "" Then
+                PATH_JAVA = Mid(fileDialog.FileName, 1, fileDialog.FileName.LastIndexOf("\"))
+                WriteReg("SetupJavaPath", PATH_JAVA)
+                log("[Guild] 人工指定的新的Java路径:" & PATH_JAVA)
+                Dim th As New Thread(AddressOf SetJavaEnvironment)
+                th.Priority = ThreadPriority.AboveNormal
+                th.Start()
+                HidePage(panJava)
+                ShowPage(panTheme)
+            End If
+        End Using
+    End Sub
+
+    '主题配色
+    Private Sub ThemeChange(sender As Object, e As EventArgs) Handles rectTheme0.MouseUp, rectTheme1.MouseUp, rectTheme2.MouseUp, rectTheme3.MouseUp
+        WriteIni("setup", "UiTheme", sender.Tag)
+        RefreshTheme()
+    End Sub
+    Private Sub btnTheme_Loaded(sender As Object, e As RoutedEventArgs) Handles btnTheme.Click
+        HidePage(panTheme)
+        ShowPage(panPlayer)
+    End Sub
+
+    '玩家种类
+    Private IsNewPlayer As Boolean
+    Private Sub btnPlayerClickNew() Handles btnPlayerNew.Click
+        IsNewPlayer = True
+        WriteIni("setup", "DownForgeAction", "0")
+        WriteIni("setup", "HomeUpdateRelease", "True")
+        HidePage(panPlayer)
+        MinecraftFolder()
+    End Sub
+    Private Sub btnPlayerClickOld() Handles btnPlayerOld.Click
+        IsNewPlayer = False
+        HidePage(panPlayer)
+        MinecraftFolder()
+    End Sub
+
+    'Minecraft 文件夹
+    Private Sub MinecraftFolder()
+        Try
+
+            '优先尝试设置为当前文件夹
+            If CheckDirectoryPermission(PATH & ".minecraft\versions") Then MinecraftThere() : Exit Sub
+
+            '获取官启目录下的版本数量
+            Dim MojangPathCount As Integer = 0
+            Dim MojangPath As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\"
+            If CheckDirectoryPermission(MojangPath & "versions\") Then MojangPathCount = Directory.GetDirectories(MojangPath & "versions\").Length
+
+            '如果官启目录下有版本的话就进行询问
+            If IsNewPlayer Then MinecraftThere() : Exit Sub
+            If MojangPathCount > 0 Then
+                HidePage(panPlayer)
+                ShowPage(panMinecraft)
+            Else
+                MinecraftThere()
+            End If
+
+        Catch ex As Exception
+            MinecraftThere()
+        End Try
+    End Sub
+    Private Sub MinecraftMojang() Handles btnMinecraftMojang.Click
+        WriteIni("setup", "LaunchFolderSelect", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\")
+        HidePage(panMinecraft)
+        PoolMinecraftFolder()
+        VersionCheck()
+    End Sub
+    Private Sub MinecraftThere() Handles btnMinecraftThere.Click
+        WriteIni("setup", "LaunchFolderSelect", PATH & ".minecraft\")
+        HidePage(panMinecraft)
+        PoolMinecraftFolder()
+        VersionCheck()
+    End Sub
+
+    '版本隔离
+    Private Sub VersionCheck()
+        '检查根目录与版本文件夹内是否有 mod
+        Dim IsRoot As Boolean = Directory.Exists(PATH_MC & "mods")
+        Dim IsVersion As Boolean = False
+        Try
+            If Directory.Exists(PATH_MC & "versions/") Then
+                For Each Dir As DirectoryInfo In New DirectoryInfo(PATH_MC & "versions/").GetDirectories
+                    If Directory.Exists(Dir.FullName & "/mods/") Then
+                        IsVersion = True
+                        GoTo FindEnd
+                    End If
+                Next
+            End If
+        Catch
+        End Try
+FindEnd:
+        '判断情况
+        If IsRoot Then
+            If IsVersion Then
+                '根目录和版本文件夹都有
+                GoTo RequireCheck
+            Else
+                '根目录有,版本文件夹没有
+                GoTo DirectNo
+            End If
+        Else
+            If IsVersion Then
+                '根目录没有,版本文件夹有
+                GoTo DirectSplit
+            Else
+                '两边都没有
+                If IsNewPlayer Then
+                    GoTo DirectNo
+                Else
+                    GoTo RequireCheck
+                End If
+            End If
+        End If
+        Exit Sub
+        '各种处理
+RequireCheck:
+        ShowPage(panVersion)
+        Exit Sub
+DirectSplit:
+        WriteIni("setup", "LaunchSplit", "1")
+        GuildExit()
+        Exit Sub
+DirectNo:
+        WriteIni("setup", "LaunchSplit", "0")
+        GuildExit()
+        Exit Sub
+    End Sub
+    Private Sub labVersionAll_Click(sender As Object, e As EventArgs) Handles labVersionAll.Click
+        WriteIni("setup", "LaunchSplit", "2")
+        GuildExit()
+    End Sub
+    Private Sub labVersionForge_Click(sender As Object, e As EventArgs) Handles labVersionForge.Click
+        WriteIni("setup", "LaunchSplit", "1")
+        GuildExit()
+    End Sub
+    Private Sub labVersionNo_Click(sender As Object, e As EventArgs) Handles labVersionNo.Click
+        WriteIni("setup", "LaunchSplit", "0")
+        GuildExit()
+    End Sub
+
+    '退出设置向导
+    Private Sub GuildExit()
+
+        ShowHint(New HintConverter("配置完成,欢迎使用 PCL!", HintState.Finish))
+        frmMain.panTopSelect.Visibility = Visibility.Visible
+        frmMain.panTopSelect.Opacity = 0
+        frmMain.panGuild.IsHitTestVisible = False
+        frmMain.labTopVer.Visibility = Visibility.Visible
+        frmMain.labTopVer.Opacity = 0
+        AniStart({
+                AaHeight(frmMain.panTop, 52, 200),
+                AaOpacity(frmMain.labFirst, -1, 200),
+                AaOpacity(frmMain.panTopSelect, 1, 150, 150),
+                AaOpacity(frmMain.labTopVer, 0.23, 150, 150),
+                AaOpacity(frmMain.panGuild, -1, 300)
+            }, "ExitGuild")
+
+    End Sub
+
+End Class

Some files were not shown because too many files changed in this diff