龙腾猫跃 1 年之前
父节点
当前提交
a41579b1da
共有 71 个文件被更改,包括 1148 次插入 和 1337 次删除
  1. 2 3
      Plain Craft Launcher 2/Application.xaml.vb
  2. 0 0
      Plain Craft Launcher 2/Controls/Behaviors/IMyRadio.vb
  3. 0 0
      Plain Craft Launcher 2/Controls/Behaviors/Interfaces.vb
  4. 3 40
      Plain Craft Launcher 2/Controls/MyButton.xaml.vb
  5. 22 15
      Plain Craft Launcher 2/Controls/MyCard.vb
  6. 1 3
      Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb
  7. 12 9
      Plain Craft Launcher 2/Controls/MyComboBox.vb
  8. 32 29
      Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb
  9. 6 6
      Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb
  10. 1 31
      Plain Craft Launcher 2/Controls/MyHint.xaml.vb
  11. 3 36
      Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb
  12. 1 34
      Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb
  13. 16 3
      Plain Craft Launcher 2/Controls/MyImage.vb
  14. 7 38
      Plain Craft Launcher 2/Controls/MyListItem.xaml.vb
  15. 3 0
      Plain Craft Launcher 2/Controls/MyMenuItem.vb
  16. 1 0
      Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb
  17. 6 10
      Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb
  18. 3 0
      Plain Craft Launcher 2/Controls/MySearchBox.xaml.vb
  19. 4 0
      Plain Craft Launcher 2/Controls/MyTextBox.vb
  20. 15 50
      Plain Craft Launcher 2/Controls/MyTextButton.vb
  21. 23 13
      Plain Craft Launcher 2/FormMain.xaml.vb
  22. 37 17
      Plain Craft Launcher 2/Modules/Base/ModBase.vb
  23. 21 19
      Plain Craft Launcher 2/Modules/Base/ModNet.vb
  24. 2 3
      Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
  25. 32 66
      Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
  26. 48 14
      Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
  27. 12 12
      Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
  28. 1 1
      Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb
  29. 292 204
      Plain Craft Launcher 2/Modules/ModEvent.vb
  30. 86 35
      Plain Craft Launcher 2/Modules/ModMain.vb
  31. 12 12
      Plain Craft Launcher 2/Modules/ModMusic.vb
  32. 4 2
      Plain Craft Launcher 2/Modules/ModSecret.vb
  33. 2 2
      Plain Craft Launcher 2/My Project/AssemblyInfo.vb
  34. 7 7
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb
  35. 18 18
      Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
  36. 11 11
      Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml
  37. 4 4
      Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb
  38. 9 9
      Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb
  39. 7 7
      Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb
  40. 1 1
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb
  41. 1 1
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb
  42. 2 2
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml
  43. 1 1
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb
  44. 3 3
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb
  45. 0 19
      Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml
  46. 1 2
      Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml
  47. 1 1
      Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml
  48. 1 20
      Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb
  49. 231 300
      Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml
  50. 2 2
      Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb
  51. 1 1
      Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb
  52. 4 2
      Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb
  53. 13 13
      Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb
  54. 3 18
      Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb
  55. 23 8
      Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb
  56. 5 5
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml
  57. 7 7
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb
  58. 1 1
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb
  59. 2 2
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb
  60. 2 1
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml
  61. 9 9
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb
  62. 1 1
      Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb
  63. 1 1
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml
  64. 7 6
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb
  65. 17 17
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
  66. 11 11
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb
  67. 5 5
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml
  68. 1 1
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb
  69. 2 2
      Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
  70. 23 111
      Plain Craft Launcher 2/Resources/Custom.xaml
  71. 二进制
      Plain Craft Launcher 2/Resources/Help.zip

+ 2 - 3
Plain Craft Launcher 2/Application.xaml.vb

@@ -78,7 +78,6 @@ Public Class Application
                 End If
                 End If
             End Try
             End Try
             Directory.CreateDirectory(PathTemp & "Cache")
             Directory.CreateDirectory(PathTemp & "Cache")
-            Directory.CreateDirectory(PathTemp & "Download")
             Directory.CreateDirectory(PathAppdata)
             Directory.CreateDirectory(PathAppdata)
             '检测单例
             '检测单例
 #If Not DEBUG Then
 #If Not DEBUG Then
@@ -124,9 +123,9 @@ WaitRetry:
             '添加日志
             '添加日志
             Log($"[Start] 程序版本:{VersionDisplayName} ({VersionCode}{If(CommitHash = "", "", $",#{CommitHash}")})")
             Log($"[Start] 程序版本:{VersionDisplayName} ({VersionCode}{If(CommitHash = "", "", $",#{CommitHash}")})")
 #If RELEASE Then
 #If RELEASE Then
-            Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",正式版", "")}")
+            Log($"[Start] 识别码:{Identify}{If(ThemeCheckOne(9), ",正式版", "")}")
 #Else
 #Else
-            Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",已解锁反馈主题", "")}")
+            Log($"[Start] 识别码:{Identify}{If(ThemeCheckOne(9), ",已解锁反馈主题", "")}")
 #End If
 #End If
             Log($"[Start] 程序路径:{PathWithName}")
             Log($"[Start] 程序路径:{PathWithName}")
             Log($"[Start] 系统编码:{Encoding.Default.HeaderName} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})")
             Log($"[Start] 系统编码:{Encoding.Default.HeaderName} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})")

+ 0 - 0
Plain Craft Launcher 2/Controls/IMyRadio.vb → Plain Craft Launcher 2/Controls/Behaviors/IMyRadio.vb


+ 0 - 0
Plain Craft Launcher 2/Controls/Interfaces.vb → Plain Craft Launcher 2/Controls/Behaviors/Interfaces.vb


+ 3 - 40
Plain Craft Launcher 2/Controls/MyButton.xaml.vb

@@ -140,51 +140,14 @@ Public Class MyButton
         End Try
         End Try
     End Sub
     End Sub
 
 
-    '实现自定义事件
+    '鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
+    Private IsMouseDown As Boolean = False
     Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
     Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
         If Not IsMouseDown Then Return
         If Not IsMouseDown Then Return
         Log("[Control] 按下按钮:" & Text)
         Log("[Control] 按下按钮:" & Text)
         RaiseEvent Click(sender, e)
         RaiseEvent Click(sender, e)
-        If Not String.IsNullOrEmpty(Tag) Then
-            If Tag.ToString.StartsWithF("链接-") OrElse Tag.ToString.StartsWithF("启动-") Then
-                Hint("主页自定义按钮语法已更新,且不再兼容老版本语法,请查看新的自定义示例!")
-            End If
-        End If
-        ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents)
+        RaiseCustomEvent() '自定义事件
     End Sub
     End Sub
-    Public Property EventType As String
-        Get
-            Return GetValue(EventTypeProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventTypeProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyButton), New PropertyMetadata(Nothing))
-    Public Property EventData As String
-        Get
-            Return GetValue(EventDataProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventDataProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyButton), New PropertyMetadata(Nothing))
-    Public Property CustomEvents As CustomEventCollection
-        Get
-            If _CustomEvents Is Nothing Then
-                _CustomEvents = New CustomEventCollection
-            End If
-            Return _CustomEvents
-        End Get
-        Set(value As CustomEventCollection)
-            _CustomEvents = value
-        End Set
-    End Property
-    Private _CustomEvents As CustomEventCollection = Nothing
-
-    '鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
-    Private IsMouseDown As Boolean = False
     Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
     Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
         IsMouseDown = True
         IsMouseDown = True
         Focus()
         Focus()

+ 22 - 15
Plain Craft Launcher 2/Controls/MyCard.vb

@@ -132,7 +132,9 @@
         For Each Data As Object In Stack.Tag
         For Each Data As Object In Stack.Tag
             Select Case Type
             Select Case Type
                 Case 0
                 Case 0
-                    Stack.Children.Add(PageSelectRight.McVersionListItem(Data))
+                    Dim VersionItem As MyListItem = CType(Data, McVersion).ToListItem
+                    VersionItem.ContentHandler = AddressOf PageSelectRight.McVersionListContent
+                    Stack.Children.Add(VersionItem)
                 Case 2
                 Case 2
                     Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True))
                     Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True))
                 Case 3
                 Case 3
@@ -309,9 +311,6 @@
         End Set
         End Set
     End Property
     End Property
     Private _IsSwapped As Boolean = False
     Private _IsSwapped As Boolean = False
-    ''' <summary>
-    ''' 是否已被折叠。(已过时,请使用 IsSwapped)
-    ''' </summary>
     <Obsolete("IsSwaped 存在拼写错误,请换用 IsSwapped 属性。")>
     <Obsolete("IsSwaped 存在拼写错误,请换用 IsSwapped 属性。")>
     Public Property IsSwaped As Boolean
     Public Property IsSwaped As Boolean
         Get
         Get
@@ -325,37 +324,45 @@
     Sub(sender, e) If sender IsNot Nothing AndAlso TypeOf sender Is MyCard Then CType(sender, MyCard).IsSwapped = CType(e.NewValue, Boolean)))
     Sub(sender, e) If sender IsNot Nothing AndAlso TypeOf sender Is MyCard Then CType(sender, MyCard).IsSwapped = CType(e.NewValue, Boolean)))
 
 
     Public Property SwapLogoRight As Boolean = False
     Public Property SwapLogoRight As Boolean = False
-    Private IsMouseDown As Boolean = False
+    Private IsSwapMouseDown As Boolean = False '用于触发卡片展开/折叠的 MouseDown
+    Private IsCustomMouseDown As Boolean = False '用于触发自定义事件的 MouseDown
     Public Event PreviewSwap(sender As Object, e As RouteEventArgs)
     Public Event PreviewSwap(sender As Object, e As RouteEventArgs)
     Public Event Swap(sender As Object, e As RouteEventArgs)
     Public Event Swap(sender As Object, e As RouteEventArgs)
     Public Const SwapedHeight As Integer = 40
     Public Const SwapedHeight As Integer = 40
     Private Sub MyCard_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
     Private Sub MyCard_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
         Dim Pos As Double = Mouse.GetPosition(Me).Y
         Dim Pos As Double = Mouse.GetPosition(Me).Y
+        If Not IsSwapped AndAlso
+            (Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return
+        IsCustomMouseDown = True
         If Not IsSwapped AndAlso
         If Not IsSwapped AndAlso
             (SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判
             (SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判
-        IsMouseDown = True
+        IsSwapMouseDown = True
     End Sub
     End Sub
-    Private Sub MyCard_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
-        If Not IsMouseDown Then Return
-        IsMouseDown = False
+    Private Sub MyCard_MouseLeftButtonUp() Handles Me.MouseLeftButtonUp
+        If Not IsCustomMouseDown Then Return
+        IsCustomMouseDown = False
+        RaiseCustomEvent() '触发自定义事件
+
+        If Not IsSwapMouseDown Then Return
+        IsSwapMouseDown = False
 
 
         Dim Pos As Double = Mouse.GetPosition(Me).Y
         Dim Pos As Double = Mouse.GetPosition(Me).Y
         If Not IsSwapped AndAlso
         If Not IsSwapped AndAlso
             (SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判
             (SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判
 
 
-        Dim ee = New RouteEventArgs(True)
-        RaiseEvent PreviewSwap(Me, ee)
-        If ee.Handled Then
-            IsMouseDown = False
+        Dim e = New RouteEventArgs(True)
+        RaiseEvent PreviewSwap(Me, e)
+        If e.Handled Then
+            IsSwapMouseDown = False
             Return
             Return
         End If
         End If
 
 
         IsSwapped = Not IsSwapped
         IsSwapped = Not IsSwapped
         Log("[Control] " & If(IsSwapped, "折叠卡片", "展开卡片") & If(Title Is Nothing, "", ":" & Title))
         Log("[Control] " & If(IsSwapped, "折叠卡片", "展开卡片") & If(Title Is Nothing, "", ":" & Title))
-        RaiseEvent Swap(Me, ee)
+        RaiseEvent Swap(Me, e)
     End Sub
     End Sub
     Private Sub MyCard_MouseLeave_Swap(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
     Private Sub MyCard_MouseLeave_Swap(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
-        IsMouseDown = False
+        IsSwapMouseDown = False
     End Sub
     End Sub
 
 
 #End Region
 #End Region

+ 1 - 3
Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb

@@ -12,9 +12,6 @@ Public Class MyCheckBox
     ''' <param name="user">是否为用户手动改变的勾选状态。</param>
     ''' <param name="user">是否为用户手动改变的勾选状态。</param>
     Public Event Change(sender As Object, user As Boolean)
     Public Event Change(sender As Object, user As Boolean)
     Public Event PreviewChange(sender As Object, e As RouteEventArgs)
     Public Event PreviewChange(sender As Object, e As RouteEventArgs)
-    Public Sub RaiseChange()
-        RaiseEvent Change(Me, False)
-    End Sub '使外部程序引发本控件的 Change 事件
 
 
     '自定义属性
     '自定义属性
     Public Property Checked As Boolean
     Public Property Checked As Boolean
@@ -59,6 +56,7 @@ Public Class MyCheckBox
 
 
             '更改动画
             '更改动画
             SyncUI()
             SyncUI()
+            RaiseCustomEvent()
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "设置 Checked 失败")
             Log(ex, "设置 Checked 失败")
         End Try
         End Try

+ 12 - 9
Plain Craft Launcher 2/Controls/MyComboBox.vb

@@ -117,15 +117,14 @@
     Private IsTextChanging As Boolean = False
     Private IsTextChanging As Boolean = False
     Private Sub MyComboBox_TextChanged(sender As Object, e As TextChangedEventArgs) Handles Me.TextChanged
     Private Sub MyComboBox_TextChanged(sender As Object, e As TextChangedEventArgs) Handles Me.TextChanged
         If IsTextChanging OrElse Not IsEditable Then Return
         If IsTextChanging OrElse Not IsEditable Then Return
-        If SelectedItem IsNot Nothing AndAlso Text <> SelectedItem.ToString Then
-            Dim RawText As String = Text
-            Dim RawSelectionStart As Integer = TextBox.SelectionStart
-            IsTextChanging = True
-            SelectedItem = Nothing
-            Text = RawText
-            TextBox.SelectionStart = RawSelectionStart
-            IsTextChanging = False
-        End If
+        If SelectedItem Is Nothing OrElse Text = SelectedItem.ToString Then Return
+        Dim RawText As String = Text
+        Dim RawSelectionStart As Integer = TextBox.SelectionStart
+        IsTextChanging = True
+        SelectedItem = Nothing
+        Text = RawText
+        TextBox.SelectionStart = RawSelectionStart
+        IsTextChanging = False
     End Sub
     End Sub
 
 
     Public ReadOnly Property ContentPresenter As ContentPresenter
     Public ReadOnly Property ContentPresenter As ContentPresenter
@@ -134,4 +133,8 @@
         End Get
         End Get
     End Property
     End Property
 
 
+    Private Sub MyComboBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles Me.SelectionChanged
+        If IsLoaded AndAlso AniControlEnabled = 0 Then RaiseCustomEvent()
+    End Sub
+
 End Class
 End Class

+ 32 - 29
Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb

@@ -53,25 +53,26 @@
         Set(value As Boolean)
         Set(value As Boolean)
             If _Show = value Then Return
             If _Show = value Then Return
             _Show = value
             _Show = value
-            RunInUi(Sub()
-                        If value Then
-                            '有了
-                            Visibility = Visibility.Visible
-                            AniStart({
-                                AaScaleTransform(Me, 0.3 - CType(RenderTransform, ScaleTransform).ScaleX, 500, 60, New AniEaseOutFluent(AniEasePower.Weak)),
-                                AaScaleTransform(Me, 0.7, 500, 60, New AniEaseOutBack(AniEasePower.Weak)),
-                                AaHeight(Me, 50 - Height, 200,, New AniEaseOutFluent(AniEasePower.Weak))
-                            }, "MyExtraButton MainScale " & Uuid)
-                        Else
-                            '没了
-                            AniStart({
-                                AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak)),
-                                AaHeight(Me, -Height, 400, 100, New AniEaseOutFluent()),
-                                AaCode(Sub() Visibility = Visibility.Collapsed,, True)
-                            }, "MyExtraButton MainScale " & Uuid)
-                        End If
-                        IsHitTestVisible = value '防止缩放动画中依然可以点进去
-                    End Sub)
+            RunInUi(
+            Sub()
+                If value Then
+                    '有了
+                    Visibility = Visibility.Visible
+                    AniStart({
+                        AaScaleTransform(Me, 0.3 - CType(RenderTransform, ScaleTransform).ScaleX, 500, 60, New AniEaseOutFluent(AniEasePower.Weak)),
+                        AaScaleTransform(Me, 0.7, 500, 60, New AniEaseOutBack(AniEasePower.Weak)),
+                        AaHeight(Me, 50 - Height, 200,, New AniEaseOutFluent(AniEasePower.Weak))
+                    }, "MyExtraButton MainScale " & Uuid)
+                Else
+                    '没了
+                    AniStart({
+                        AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak)),
+                        AaHeight(Me, -Height, 400, 100, New AniEaseOutFluent()),
+                        AaCode(Sub() Visibility = Visibility.Collapsed,, True)
+                    }, "MyExtraButton MainScale " & Uuid)
+                End If
+                IsHitTestVisible = value '防止缩放动画中依然可以点进去
+            End Sub)
         End Set
         End Set
     End Property
     End Property
     Public Delegate Function ShowCheckDelegate() As Boolean
     Public Delegate Function ShowCheckDelegate() As Boolean
@@ -137,6 +138,7 @@
                 AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
                 AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
             }, "MyExtraButton Scale " & Uuid)
             }, "MyExtraButton Scale " & Uuid)
         End If
         End If
+        If IsLeftMouseHeld Then RaiseCustomEvent()
         IsLeftMouseHeld = False
         IsLeftMouseHeld = False
         RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
         RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
     End Sub
     End Sub
@@ -199,16 +201,17 @@
     ''' 发出一圈波浪效果提示。
     ''' 发出一圈波浪效果提示。
     ''' </summary>
     ''' </summary>
     Public Sub Ribble()
     Public Sub Ribble()
-        RunInUi(Sub()
-                    Dim Shape As New Border With {.CornerRadius = New CornerRadius(1000), .BorderThickness = New Thickness(0.001), .Opacity = 0.5, .RenderTransformOrigin = New Point(0.5, 0.5), .RenderTransform = New ScaleTransform()}
-                    Shape.SetResourceReference(Border.BackgroundProperty, "ColorBrush5")
-                    PanScale.Children.Insert(0, Shape)
-                    AniStart({
-                       AaScaleTransform(Shape, 13, 1000, Ease:=New AniEaseInoutFluent(AniEasePower.Strong, 0.3)),
-                       AaOpacity(Shape, -Shape.Opacity, 1000),
-                       AaCode(Sub() PanScale.Children.Remove(Shape), After:=True)
-                    }, "ExtraButton Ribble " & GetUuid())
-                End Sub)
+        RunInUi(
+        Sub()
+            Dim Shape As New Border With {.CornerRadius = New CornerRadius(1000), .BorderThickness = New Thickness(0.001), .Opacity = 0.5, .RenderTransformOrigin = New Point(0.5, 0.5), .RenderTransform = New ScaleTransform()}
+            Shape.SetResourceReference(Border.BackgroundProperty, "ColorBrush5")
+            PanScale.Children.Insert(0, Shape)
+            AniStart({
+                AaScaleTransform(Shape, 13, 1000, Ease:=New AniEaseInoutFluent(AniEasePower.Strong, 0.3)),
+                AaOpacity(Shape, -Shape.Opacity, 1000),
+                AaCode(Sub() PanScale.Children.Remove(Shape), After:=True)
+            }, "ExtraButton Ribble " & GetUuid())
+        End Sub)
     End Sub
     End Sub
 
 
 End Class
 End Class

+ 6 - 6
Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb

@@ -81,12 +81,12 @@ Public Class MyExtraTextButton
 
 
     '触发点击事件
     '触发点击事件
     Private Sub Button_LeftMouseUp(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonUp
     Private Sub Button_LeftMouseUp(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonUp
-        If IsLeftMouseHeld Then
-            Log("[Control] 按下附加图标按钮:" & Text)
-            RaiseEvent Click(sender, e)
-            e.Handled = True
-            Button_LeftMouseUp()
-        End If
+        If Not IsLeftMouseHeld Then Return
+        Log("[Control] 按下附加图标按钮:" & Text)
+        RaiseEvent Click(sender, e)
+        e.Handled = True
+        RaiseCustomEvent()
+        Button_LeftMouseUp()
     End Sub
     End Sub
 
 
     '鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
     '鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)

+ 1 - 31
Plain Craft Launcher 2/Controls/MyHint.xaml.vb

@@ -113,7 +113,7 @@ Public Class MyHint
         IsMouseDown = False
         IsMouseDown = False
         Log("[Control] 按下提示条" & If(String.IsNullOrEmpty(Name), "", ":" & Name))
         Log("[Control] 按下提示条" & If(String.IsNullOrEmpty(Name), "", ":" & Name))
         e.Handled = True
         e.Handled = True
-        ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents)
+        RaiseCustomEvent() '自定义事件
     End Sub
     End Sub
     Private Sub MyHint_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
     Private Sub MyHint_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
         IsMouseDown = True
         IsMouseDown = True
@@ -121,36 +121,6 @@ Public Class MyHint
     Private Sub MyHint_MouseLeave() Handles Me.MouseLeave
     Private Sub MyHint_MouseLeave() Handles Me.MouseLeave
         IsMouseDown = False
         IsMouseDown = False
     End Sub
     End Sub
-    Public Property EventType As String
-        Get
-            Return GetValue(EventTypeProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventTypeProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyHint), New PropertyMetadata(Nothing))
-    Public Property EventData As String
-        Get
-            Return GetValue(EventDataProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventDataProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyHint), New PropertyMetadata(Nothing))
-    Public Property CustomEvents As CustomEventCollection
-        Get
-            If _CustomEvents Is Nothing Then
-                _CustomEvents = New CustomEventCollection
-            End If
-            Return _CustomEvents
-        End Get
-        Set(value As CustomEventCollection)
-            _CustomEvents = value
-        End Set
-    End Property
-    Private _CustomEvents As CustomEventCollection = Nothing
 
 
 End Class
 End Class
 Partial Public Module ModAnimation
 Partial Public Module ModAnimation

+ 3 - 36
Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb

@@ -48,48 +48,16 @@
         End Set
         End Set
     End Property
     End Property
 
 
-    '触发点击事件
+    '鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
+    Private IsMouseDown As Boolean = False
     Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
     Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
         If Not IsMouseDown Then Return
         If Not IsMouseDown Then Return
         Log("[Control] 按下图标按钮" & If(String.IsNullOrEmpty(Name), "", ":" & Name))
         Log("[Control] 按下图标按钮" & If(String.IsNullOrEmpty(Name), "", ":" & Name))
         RaiseEvent Click(sender, e)
         RaiseEvent Click(sender, e)
         e.Handled = True
         e.Handled = True
         Button_MouseUp()
         Button_MouseUp()
-        ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents)
+        RaiseCustomEvent() '自定义事件
     End Sub
     End Sub
-    Public Property EventType As String
-        Get
-            Return GetValue(EventTypeProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventTypeProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyIconButton), New PropertyMetadata(Nothing))
-    Public Property EventData As String
-        Get
-            Return GetValue(EventDataProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventDataProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyIconButton), New PropertyMetadata(Nothing))
-    Public Property CustomEvents As CustomEventCollection
-        Get
-            If _CustomEvents Is Nothing Then
-                _CustomEvents = New CustomEventCollection
-            End If
-            Return _CustomEvents
-        End Get
-        Set(value As CustomEventCollection)
-            _CustomEvents = value
-        End Set
-    End Property
-    Private _CustomEvents As CustomEventCollection = Nothing
-
-    '鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
-    Private IsMouseDown As Boolean = False
     Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
     Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
         IsMouseDown = True
         IsMouseDown = True
         Focus()
         Focus()
@@ -114,7 +82,6 @@
         RefreshAnim() '直接刷新颜色以判断是否已触发 MouseLeave
         RefreshAnim() '直接刷新颜色以判断是否已触发 MouseLeave
     End Sub
     End Sub
 
 
-    '自定义事件
     '务必放在 IsMouseDown 更新之后
     '务必放在 IsMouseDown 更新之后
     Private Const AnimationColorIn As Integer = 120
     Private Const AnimationColorIn As Integer = 120
     Private Const AnimationColorOut As Integer = 150
     Private Const AnimationColorOut As Integer = 150

+ 1 - 34
Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb

@@ -8,9 +8,6 @@ Public Class MyIconTextButton
     Public Uuid As Integer = GetUuid()
     Public Uuid As Integer = GetUuid()
     Public Event Check(sender As Object, raiseByMouse As Boolean)
     Public Event Check(sender As Object, raiseByMouse As Boolean)
     Public Event Change(sender As Object, raiseByMouse As Boolean)
     Public Event Change(sender As Object, raiseByMouse As Boolean)
-    Public Sub RaiseChange()
-        RaiseEvent Change(Me, False)
-    End Sub '使外部程序可以引发本控件的 Change 事件
 
 
     '自定义属性
     '自定义属性
 
 
@@ -76,7 +73,7 @@ Public Class MyIconTextButton
         Log("[Control] 按下带图标按钮:" & Text)
         Log("[Control] 按下带图标按钮:" & Text)
         IsMouseDown = False
         IsMouseDown = False
         RaiseEvent Click(Me, New RouteEventArgs(True))
         RaiseEvent Click(Me, New RouteEventArgs(True))
-        ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents)
+        RaiseCustomEvent() '自定义事件
         RefreshColor()
         RefreshColor()
     End Sub
     End Sub
     Private Sub MyIconTextButton_MouseDown() Handles Me.MouseLeftButtonDown
     Private Sub MyIconTextButton_MouseDown() Handles Me.MouseLeftButtonDown
@@ -87,36 +84,6 @@ Public Class MyIconTextButton
         IsMouseDown = False
         IsMouseDown = False
         RefreshColor()
         RefreshColor()
     End Sub
     End Sub
-    Public Property EventType As String
-        Get
-            Return GetValue(EventTypeProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventTypeProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(Nothing))
-    Public Property EventData As String
-        Get
-            Return GetValue(EventDataProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventDataProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(Nothing))
-    Public Property CustomEvents As CustomEventCollection
-        Get
-            If _CustomEvents Is Nothing Then
-                _CustomEvents = New CustomEventCollection
-            End If
-            Return _CustomEvents
-        End Get
-        Set(value As CustomEventCollection)
-            _CustomEvents = value
-        End Set
-    End Property
-    Private _CustomEvents As CustomEventCollection = Nothing
 
 
     '动画
     '动画
 
 

+ 16 - 3
Plain Craft Launcher 2/Controls/MyImage.vb

@@ -46,7 +46,7 @@
     Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString())))
     Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString())))
 
 
     ''' <summary>
     ''' <summary>
-    ''' 若 Source 是一个网络图片,该地址将作为第二下载源。
+    ''' 若 Source 是一个网络图片,该地址将作为第二图片源。
     ''' </summary>
     ''' </summary>
     Public Property FallbackSource As String
     Public Property FallbackSource As String
         Get
         Get
@@ -120,11 +120,12 @@
         End If
         End If
         RunInNewThread(
         RunInNewThread(
         Sub()
         Sub()
+            Dim IsLocalFallback As Boolean = FallbackSource IsNot Nothing AndAlso Not FallbackSource.StartsWithF("http")
             Try
             Try
                 '下载
                 '下载
                 ActualSource = LoadingSource '显示加载中的占位图片
                 ActualSource = LoadingSource '显示加载中的占位图片
                 NetDownloadByLoader(
                 NetDownloadByLoader(
-                    If(String.IsNullOrEmpty(FallbackSource), {Source}, {Source, FallbackSource}),
+                    If(String.IsNullOrEmpty(FallbackSource) OrElse IsLocalFallback, {Source}, {Source, FallbackSource}),
                     TempPath, SimulateBrowserHeaders:=True)
                     TempPath, SimulateBrowserHeaders:=True)
                 If EnableCache Then
                 If EnableCache Then
                     '保存缓存并显示
                     '保存缓存并显示
@@ -139,7 +140,19 @@
                     If TempPath IsNot Nothing Then File.Delete(TempPath)
                     If TempPath IsNot Nothing Then File.Delete(TempPath)
                 Catch
                 Catch
                 End Try
                 End Try
-                Log(ex, $"下载图片失败", LogLevel.Hint)
+                '加载本地备用图片
+                If IsLocalFallback Then
+                    Try
+                        RunInUiWait(Sub() ActualSource = FallbackSource)
+                        Log(ex, $"下载图片失败,使用本地备用图片({Source})")
+                        Return
+                    Catch exx As Exception
+                        Log(ex, $"下载图片失败({Source})", LogLevel.Hint)
+                        Log(exx, $"加载备用图片失败({FallbackSource})", LogLevel.Hint)
+                    End Try
+                Else
+                    Log(ex, $"下载图片失败({Source},备用:{FallbackSource})", LogLevel.Hint)
+                End If
             End Try
             End Try
         End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal)
         End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal)
     End Sub
     End Sub

+ 7 - 38
Plain Craft Launcher 2/Controls/MyListItem.xaml.vb

@@ -506,8 +506,8 @@ Public Class MyListItem
         RaiseEvent Click(sender, e)
         RaiseEvent Click(sender, e)
         If e.Handled Then Return
         If e.Handled Then Return
         '触发自定义事件
         '触发自定义事件
-        If Not String.IsNullOrEmpty(EventType) Then
-            ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents)
+        If CustomEventService.GetEventType(sender) <> CustomEvent.EventType.None Then
+            RaiseCustomEvent()
             e.Handled = True
             e.Handled = True
         End If
         End If
         If e.Handled Then Return
         If e.Handled Then Return
@@ -537,38 +537,6 @@ Public Class MyListItem
         If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = True
         If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = True
     End Sub
     End Sub
 
 
-    '实现自定义事件
-    Public Property EventType As String
-        Get
-            Return GetValue(EventTypeProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventTypeProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyListItem), New PropertyMetadata(Nothing))
-    Public Property EventData As String
-        Get
-            Return GetValue(EventDataProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventDataProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyListItem), New PropertyMetadata(Nothing))
-    Public Property CustomEvents As CustomEventCollection
-        Get
-            If _CustomEvents Is Nothing Then
-                _CustomEvents = New CustomEventCollection
-            End If
-            Return _CustomEvents
-        End Get
-        Set(value As CustomEventCollection)
-            _CustomEvents = value
-        End Set
-    End Property
-    Private _CustomEvents As CustomEventCollection = Nothing
-
 #End Region
 #End Region
 
 
     Private StateLast As String
     Private StateLast As String
@@ -669,13 +637,14 @@ Public Class MyListItem
             SetResourceReference(ForegroundProperty, "ColorBrush1")
             SetResourceReference(ForegroundProperty, "ColorBrush1")
         End If
         End If
         ColumnPaddingRight.Width = New GridLength(MinPaddingRight)
         ColumnPaddingRight.Width = New GridLength(MinPaddingRight)
-        If EventType = "打开帮助" AndAlso Not (Title <> "" AndAlso Info <> "") Then '#3266
+        If CustomEventService.GetEventType(Me) = CustomEvent.EventType.打开帮助 AndAlso Not (Title <> "" AndAlso Info <> "") Then '#3266
             Try
             Try
-                Dim Unused = New HelpEntry(GetEventAbsoluteUrls(EventData, EventType)(0)).SetToListItem(Me)
+                Dim Entry As New HelpEntry(CustomEvent.GetAbsoluteUrls(CustomEventService.GetEventData(Me), CustomEventService.GetEventType(Me))(0))
+                Entry.SetToListItem(Me)
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "设置帮助 MyListItem 失败", LogLevel.Msgbox)
                 Log(ex, "设置帮助 MyListItem 失败", LogLevel.Msgbox)
-                EventType = Nothing
-                EventData = Nothing
+                CustomEventService.SetEventType(Me, CustomEvent.EventType.None)
+                CustomEventService.SetEventData(Me, "")
             End Try
             End Try
         End If
         End If
     End Sub
     End Sub

+ 3 - 0
Plain Craft Launcher 2/Controls/MyMenuItem.vb

@@ -54,4 +54,7 @@
         End If
         End If
     End Sub
     End Sub
 
 
+    Private Sub MyMenuItem_Click(sender As Object, e As RoutedEventArgs) Handles Me.Click
+        RaiseCustomEvent()
+    End Sub
 End Class
 End Class

+ 1 - 0
Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb

@@ -93,6 +93,7 @@ Public Class MyRadioBox
             If IsChanged Then
             If IsChanged Then
                 If Checked Then RaiseEvent Check(Me, New RouteEventArgs(user))
                 If Checked Then RaiseEvent Check(Me, New RouteEventArgs(user))
                 RaiseEvent Changed(Me, New RouteEventArgs(user))
                 RaiseEvent Changed(Me, New RouteEventArgs(user))
+                RaiseCustomEvent()
             End If
             End If
 
 
             '更改动画
             '更改动画

+ 6 - 10
Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb

@@ -7,10 +7,6 @@ Public Class MyRadioButton
 
 
     Public Uuid As Integer = GetUuid()
     Public Uuid As Integer = GetUuid()
     Public Event Check(sender As Object, raiseByMouse As Boolean)
     Public Event Check(sender As Object, raiseByMouse As Boolean)
-    Public Event Change(sender As Object, raiseByMouse As Boolean)
-    Public Sub RaiseChange()
-        RaiseEvent Change(Me, False)
-    End Sub '使外部程序可以引发本控件的 Change 事件
 
 
     '自定义属性
     '自定义属性
 
 
@@ -46,23 +42,22 @@ Public Class MyRadioButton
     ''' 手动设置 Checked 属性。
     ''' 手动设置 Checked 属性。
     ''' </summary>
     ''' </summary>
     ''' <param name="value">新的 Checked 属性。</param>
     ''' <param name="value">新的 Checked 属性。</param>
-    ''' <param name="user">是否由用户引发。</param>
+    ''' <param name="raiseByMouse">是否由用户引发。</param>
     ''' <param name="anime">是否执行动画。</param>
     ''' <param name="anime">是否执行动画。</param>
-    Public Sub SetChecked(value As Boolean, user As Boolean, anime As Boolean)
+    Public Sub SetChecked(value As Boolean, raiseByMouse As Boolean, anime As Boolean)
         Try
         Try
 
 
             '自定义属性基础
             '自定义属性基础
 
 
             Dim IsChanged As Boolean = False
             Dim IsChanged As Boolean = False
-            If IsLoaded AndAlso Not value = _Checked Then RaiseEvent Change(Me, user)
-            If Not value = _Checked Then
+            If _Checked <> value Then
                 _Checked = value
                 _Checked = value
                 IsChanged = True
                 IsChanged = True
             End If
             End If
 
 
             '保证只有一个单选框选中
             '保证只有一个单选框选中
 
 
-            If IsNothing(Parent) Then Return
+            If Parent Is Nothing Then Return
             Dim RadioboxList As New List(Of MyRadioButton)
             Dim RadioboxList As New List(Of MyRadioButton)
             Dim CheckedCount As Integer = 0
             Dim CheckedCount As Integer = 0
             '收集控件列表与选中个数
             '收集控件列表与选中个数
@@ -105,7 +100,8 @@ Public Class MyRadioButton
             RefreshColor(Nothing, anime)
             RefreshColor(Nothing, anime)
 
 
             '触发事件
             '触发事件
-            If Checked Then RaiseEvent Check(Me, user)
+            If Checked Then RaiseEvent Check(Me, raiseByMouse)
+            RaiseCustomEvent()
 
 
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "单选按钮勾选改变错误", LogLevel.Hint)
             Log(ex, "单选按钮勾选改变错误", LogLevel.Hint)

+ 3 - 0
Plain Craft Launcher 2/Controls/MySearchBox.xaml.vb

@@ -39,4 +39,7 @@
         TextBox.Focus()
         TextBox.Focus()
     End Sub
     End Sub
 
 
+    Private Sub MySearchBox_KeyUp(sender As Object, e As KeyEventArgs) Handles Me.KeyUp
+        If e.Key = Key.Enter Then RaiseCustomEvent()
+    End Sub
 End Class
 End Class

+ 4 - 0
Plain Craft Launcher 2/Controls/MyTextBox.vb

@@ -291,4 +291,8 @@
         End If
         End If
     End Sub
     End Sub
 
 
+    '在按下回车时触发自定义事件
+    Private Sub MyTextBox_KeyUp(sender As Object, e As KeyEventArgs) Handles Me.KeyUp
+        If e.Key = Key.Enter Then RaiseCustomEvent()
+    End Sub
 End Class
 End Class

+ 15 - 50
Plain Craft Launcher 2/Controls/MyTextButton.vb

@@ -22,15 +22,15 @@
         End Set
         End Set
     End Property
     End Property
     Public Shared ReadOnly TextProperty As DependencyProperty =
     Public Shared ReadOnly TextProperty As DependencyProperty =
-        DependencyProperty.Register("Text", GetType(String), GetType(MyTextButton), New PropertyMetadata("", Sub(sender As MyTextButton, e As DependencyPropertyChangedEventArgs)
-                                                                                                                 If Not e.OldValue = e.NewValue Then
-                                                                                                                     AniStart({
-                                                                                                                              AaOpacity(sender, -sender.Opacity, 50),
-                                                                                                                              AaCode(Sub() sender.Content = e.NewValue,, True),
-                                                                                                                              AaOpacity(sender, 1, 170)
-                                                                                                                        }, "MyTextButton Text " & sender.Uuid)
-                                                                                                                 End If
-                                                                                                             End Sub))
+        DependencyProperty.Register("Text", GetType(String), GetType(MyTextButton), New PropertyMetadata("",
+        Sub(sender As MyTextButton, e As DependencyPropertyChangedEventArgs)
+            If e.OldValue = e.NewValue Then Return
+            AniStart({
+                AaOpacity(sender, -sender.Opacity, 50),
+                AaCode(Sub() sender.Content = e.NewValue,, True),
+                AaOpacity(sender, 1, 170)
+            }, "MyTextButton Text " & sender.Uuid)
+        End Sub))
 
 
     '鼠标事件
     '鼠标事件
 
 
@@ -43,13 +43,12 @@
         IsMouseDown = False
         IsMouseDown = False
     End Sub
     End Sub
     Private Sub MyTextButton_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp
     Private Sub MyTextButton_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp
-        If IsMouseDown Then
-            IsMouseDown = False
-            Log("[Control] 按下文本按钮:" & Text)
-            RaiseEvent Click(Me, Nothing)
-            ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents)
-            e.Handled = True
-        End If
+        If Not IsMouseDown Then Return
+        IsMouseDown = False
+        Log("[Control] 按下文本按钮:" & Text)
+        RaiseEvent Click(Me, Nothing)
+        RaiseCustomEvent()
+        e.Handled = True
     End Sub
     End Sub
 
 
     '指向动画
     '指向动画
@@ -85,38 +84,4 @@
         End If
         End If
     End Sub
     End Sub
 
 
-    '实现自定义事件
-    Public Property EventType As String
-        Get
-            Return GetValue(EventTypeProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventTypeProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register(
-        "EventType", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
-    Public Property EventData As String
-        Get
-            Return GetValue(EventDataProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventDataProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register(
-        "EventData", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
-    Public Property CustomEvents As CustomEventCollection
-        Get
-            If _CustomEvents Is Nothing Then
-                _CustomEvents = New CustomEventCollection
-            End If
-            Return _CustomEvents
-        End Get
-        Set(value As CustomEventCollection)
-            _CustomEvents = value
-        End Set
-    End Property
-    Private _CustomEvents As CustomEventCollection = Nothing
-
 End Class
 End Class

+ 23 - 13
Plain Craft Launcher 2/FormMain.xaml.vb

@@ -104,6 +104,15 @@ Public Class FormMain
         '3:BUG+ IMP* FEAT-
         '3:BUG+ IMP* FEAT-
         '2:BUG* IMP-
         '2:BUG* IMP-
         '1:BUG-
         '1:BUG-
+        If LastVersion < 371 Then 'Snapshot 2.10.9
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:如果版本设置了自定义描述,会在标题后面以淡灰色显示其版本号"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "新增:支持为一个控件设置多个自定义事件"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "新增:修改变量、弹出提示自定义事件"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "新增:添加大量替换标记,允许在更多设置和 XAML 中使用更多替换标记"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法安装 MMC 整合包"))
+            FeatureCount += 33
+            BugCount += 11
+        End If
         If LastVersion < 370 Then 'Snapshot 2.10.8
         If LastVersion < 370 Then 'Snapshot 2.10.8
             If LastVersion >= 368 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:网络与下载稳定性优化"))
             If LastVersion >= 368 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:网络与下载稳定性优化"))
             FeatureCount += 3
             FeatureCount += 3
@@ -318,7 +327,7 @@ Public Class FormMain
         FrmLaunchRight.PageState = MyPageRight.PageStates.ContentStay
         FrmLaunchRight.PageState = MyPageRight.PageStates.ContentStay
         '模式提醒
         '模式提醒
 #If DEBUG Then
 #If DEBUG Then
-        Hint("[开发者模式] PCL 正以开发者模式运行,这可能会造成严重的性能下降,请务必立即向开发者反馈此问题!", HintType.Critical)
+        Hint("[开发者模式] PCL 正以开发者模式运行,这可能会造成严重的性能下降,请务必立即向开发者反馈此问题!", HintType.Red)
 #End If
 #End If
         If ModeDebug Then Hint("[调试模式] PCL 正以调试模式运行,这可能会导致性能下降,若无必要请不要开启!")
         If ModeDebug Then Hint("[调试模式] PCL 正以调试模式运行,这可能会导致性能下降,若无必要请不要开启!")
         '尽早执行的加载池
         '尽早执行的加载池
@@ -687,9 +696,9 @@ Public Class FormMain
         If e.Key = Key.F12 Then
         If e.Key = Key.F12 Then
             PageSetupUI.HiddenForceShow = Not PageSetupUI.HiddenForceShow
             PageSetupUI.HiddenForceShow = Not PageSetupUI.HiddenForceShow
             If PageSetupUI.HiddenForceShow Then
             If PageSetupUI.HiddenForceShow Then
-                Hint("功能隐藏设置已暂时关闭!", HintType.Finish)
+                Hint("功能隐藏设置已暂时关闭!", HintType.Green)
             Else
             Else
-                Hint("功能隐藏设置已重新开启!", HintType.Finish)
+                Hint("功能隐藏设置已重新开启!", HintType.Green)
             End If
             End If
             PageSetupUI.HiddenRefresh()
             PageSetupUI.HiddenRefresh()
             Return
             Return
@@ -785,12 +794,12 @@ Public Class FormMain
                         Dim AuthlibServer As String = Net.WebUtility.UrlDecode(Str.Substring("authlib-injector:yggdrasil-server:".Length))
                         Dim AuthlibServer As String = Net.WebUtility.UrlDecode(Str.Substring("authlib-injector:yggdrasil-server:".Length))
                         Log("[System] Authlib 拖拽:" & AuthlibServer)
                         Log("[System] Authlib 拖拽:" & AuthlibServer)
                         If Not String.IsNullOrEmpty(New ValidateHttp().Validate(AuthlibServer)) Then
                         If Not String.IsNullOrEmpty(New ValidateHttp().Validate(AuthlibServer)) Then
-                            Hint($"输入的 Authlib 验证服务器不符合网址格式({AuthlibServer})!", HintType.Critical)
+                            Hint($"输入的 Authlib 验证服务器不符合网址格式({AuthlibServer})!", HintType.Red)
                             Return
                             Return
                         End If
                         End If
                         Dim TargetVersion = If(PageCurrent = PageType.VersionSetup, PageVersionLeft.Version, McVersionCurrent)
                         Dim TargetVersion = If(PageCurrent = PageType.VersionSetup, PageVersionLeft.Version, McVersionCurrent)
                         If TargetVersion Is Nothing Then
                         If TargetVersion Is Nothing Then
-                            Hint("请先下载游戏,再设置第三方登录!", HintType.Critical)
+                            Hint("请先下载游戏,再设置第三方登录!", HintType.Red)
                             Return
                             Return
                         End If
                         End If
                         If AuthlibServer = "https://littleskin.cn/api/yggdrasil" Then
                         If AuthlibServer = "https://littleskin.cn/api/yggdrasil" Then
@@ -837,7 +846,7 @@ Public Class FormMain
                 '获取文件并检查
                 '获取文件并检查
                 Dim FilePathRaw = e.Data.GetData(DataFormats.FileDrop)
                 Dim FilePathRaw = e.Data.GetData(DataFormats.FileDrop)
                 If FilePathRaw Is Nothing Then '#2690
                 If FilePathRaw Is Nothing Then '#2690
-                    Hint("请将文件解压后再拖入!", HintType.Critical)
+                    Hint("请将文件解压后再拖入!", HintType.Red)
                     Return
                     Return
                 End If
                 End If
                 e.Handled = True
                 e.Handled = True
@@ -855,10 +864,10 @@ Public Class FormMain
             Log("[System] 接受文件拖拽:" & FilePath & If(FilePathList.Any, $" 等 {FilePathList.Count} 个文件", ""), LogLevel.Developer)
             Log("[System] 接受文件拖拽:" & FilePath & If(FilePathList.Any, $" 等 {FilePathList.Count} 个文件", ""), LogLevel.Developer)
             '基础检查
             '基础检查
             If Directory.Exists(FilePathList.First) AndAlso Not File.Exists(FilePathList.First) Then
             If Directory.Exists(FilePathList.First) AndAlso Not File.Exists(FilePathList.First) Then
-                Hint("请拖入一个文件,而非文件夹!", HintType.Critical)
+                Hint("请拖入一个文件,而非文件夹!", HintType.Red)
                 Return
                 Return
             ElseIf Not File.Exists(FilePathList.First) Then
             ElseIf Not File.Exists(FilePathList.First) Then
-                Hint("拖入的文件不存在:" & FilePathList.First, HintType.Critical)
+                Hint("拖入的文件不存在:" & FilePathList.First, HintType.Red)
                 Return
                 Return
             End If
             End If
             '多文件拖拽
             '多文件拖拽
@@ -866,7 +875,7 @@ Public Class FormMain
                 '必须要求全部为 jar 文件
                 '必须要求全部为 jar 文件
                 For Each File In FilePathList
                 For Each File In FilePathList
                     If Not {"jar", "litemod", "disabled", "old"}.Contains(File.AfterLast(".").ToLower) Then
                     If Not {"jar", "litemod", "disabled", "old"}.Contains(File.AfterLast(".").ToLower) Then
-                        Hint("一次请只拖入一个文件!", HintType.Critical)
+                        Hint("一次请只拖入一个文件!", HintType.Red)
                         Return
                         Return
                     End If
                     End If
                 Next
                 Next
@@ -885,7 +894,7 @@ Public Class FormMain
                 Sub()
                 Sub()
                     Setup.Set("UiCustomType", 1)
                     Setup.Set("UiCustomType", 1)
                     FrmLaunchRight.ForceRefresh()
                     FrmLaunchRight.ForceRefresh()
-                    Hint("已加载主页自定义文件!", HintType.Finish)
+                    Hint("已加载主页自定义文件!", HintType.Green)
                 End Sub)
                 End Sub)
                 Return
                 Return
             End If
             End If
@@ -995,9 +1004,9 @@ Public Class FormMain
 #Region "切换页面"
 #Region "切换页面"
 
 
     '页面种类与属性
     '页面种类与属性
-    '注意,这一枚举在 “切换页面” EventType 中调用,应视作公开 API 的一部分
     ''' <summary>
     ''' <summary>
     ''' 页面种类。
     ''' 页面种类。
+    ''' 该枚举在自定义事件中使用,是公开 API 的一部分。
     ''' </summary>
     ''' </summary>
     Public Enum PageType
     Public Enum PageType
         ''' <summary>
         ''' <summary>
@@ -1043,6 +1052,7 @@ Public Class FormMain
     End Enum
     End Enum
     ''' <summary>
     ''' <summary>
     ''' 次要页面种类。其数值必须与 StackPanel 中的下标一致。
     ''' 次要页面种类。其数值必须与 StackPanel 中的下标一致。
+    ''' 该枚举在自定义事件中使用,是公开 API 的一部分。
     ''' </summary>
     ''' </summary>
     Public Enum PageSubType
     Public Enum PageSubType
         [Default] = 0
         [Default] = 0
@@ -1485,7 +1495,7 @@ Public Class FormMain
     '投降
     '投降
     Public Sub AprilGiveup() Handles BtnExtraApril.Click
     Public Sub AprilGiveup() Handles BtnExtraApril.Click
         If IsAprilEnabled AndAlso Not IsAprilGiveup Then
         If IsAprilEnabled AndAlso Not IsAprilGiveup Then
-            Hint("=D", HintType.Finish)
+            Hint("=D", HintType.Green)
             IsAprilGiveup = True
             IsAprilGiveup = True
             FrmLaunchLeft.AprilScaleTrans.ScaleX = 1
             FrmLaunchLeft.AprilScaleTrans.ScaleX = 1
             FrmLaunchLeft.AprilScaleTrans.ScaleY = 1
             FrmLaunchLeft.AprilScaleTrans.ScaleY = 1
@@ -1503,7 +1513,7 @@ Public Class FormMain
             For Each Watcher In McWatcherList
             For Each Watcher In McWatcherList
                 Watcher.Kill()
                 Watcher.Kill()
             Next
             Next
-            Hint("已关闭运行中的 Minecraft!", HintType.Finish)
+            Hint("已关闭运行中的 Minecraft!", HintType.Green)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "强制关闭所有 Minecraft 失败", LogLevel.Feedback)
             Log(ex, "强制关闭所有 Minecraft 失败", LogLevel.Feedback)
         End Try
         End Try

+ 37 - 17
Plain Craft Launcher 2/Modules/Base/ModBase.vb

@@ -13,13 +13,13 @@ Public Module ModBase
 #Region "声明"
 #Region "声明"
 
 
     '下列版本信息由更新器自动修改
     '下列版本信息由更新器自动修改
-    Public Const VersionBaseName As String = "2.10.8" '不含分支前缀的显示用版本名
-    Public Const VersionStandardCode As String = "2.10.8." & VersionBranchCode '标准格式的四段式版本号
+    Public Const VersionBaseName As String = "2.10.9" '不含分支前缀的显示用版本名
+    Public Const VersionStandardCode As String = "2.10.9." & VersionBranchCode '标准格式的四段式版本号
     Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
     Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
 #If BETA Then
 #If BETA Then
-    Public Const VersionCode As Integer = 367 'Release
+    Public Const VersionCode As Integer = 369 'Release
 #Else
 #Else
-    Public Const VersionCode As Integer = 370 'Snapshot
+    Public Const VersionCode As Integer = 371 'Snapshot
 #End If
 #End If
     '自动生成的版本信息
     '自动生成的版本信息
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -69,7 +69,7 @@ Public Module ModBase
     ''' <summary>
     ''' <summary>
     ''' 识别码。
     ''' 识别码。
     ''' </summary>
     ''' </summary>
-    Public UniqueAddress As String = SecretGetUniqueAddress()
+    Public Identify As String = SecretGetIdentify()
     ''' <summary>
     ''' <summary>
     ''' 程序是否已结束。
     ''' 程序是否已结束。
     ''' </summary>
     ''' </summary>
@@ -761,7 +761,6 @@ Public Module ModBase
         If FilePath.Contains("\") Then FilePath = FilePath.Substring(FilePath.LastIndexOfF("\") + 1)
         If FilePath.Contains("\") Then FilePath = FilePath.Substring(FilePath.LastIndexOfF("\") + 1)
         Dim length As Integer = FilePath.Length
         Dim length As Integer = FilePath.Length
         If length = 0 Then Throw New Exception("不包含文件名:" & FilePath)
         If length = 0 Then Throw New Exception("不包含文件名:" & FilePath)
-        If length > 250 Then Throw New PathTooLongException("文件名过长:" & FilePath)
         Return FilePath
         Return FilePath
     End Function
     End Function
     ''' <summary>
     ''' <summary>
@@ -1400,12 +1399,13 @@ RetryDir:
 
 
     ''' <summary>
     ''' <summary>
     ''' 若路径长度大于指定值,则将长路径转换为短路径。
     ''' 若路径长度大于指定值,则将长路径转换为短路径。
+    ''' 如果路径不存在,则返回原始路径。
     ''' </summary>
     ''' </summary>
     Public Function ShortenPath(LongPath As String) As String
     Public Function ShortenPath(LongPath As String) As String
         If LongPath.Length <= 200 OrElse LongPath.StartsWithF("http", True) Then Return LongPath
         If LongPath.Length <= 200 OrElse LongPath.StartsWithF("http", True) Then Return LongPath
         If Not Directory.Exists(LongPath) AndAlso Not File.Exists(LongPath) Then Return LongPath
         If Not Directory.Exists(LongPath) AndAlso Not File.Exists(LongPath) Then Return LongPath
         Dim ShortPath As New StringBuilder(260)
         Dim ShortPath As New StringBuilder(260)
-        GetShortPathName(LongPath, ShortPath, 260)
+        GetShortPathName(LongPath.Replace("/", "\"), ShortPath, 260) '第一个参数也会在调用后被修改,使用 Replace 后不会影响原始字符串
         ShortenPath = ShortPath.ToString
         ShortenPath = ShortPath.ToString
         If String.IsNullOrEmpty(ShortenPath) Then Return LongPath
         If String.IsNullOrEmpty(ShortenPath) Then Return LongPath
     End Function
     End Function
@@ -1511,6 +1511,19 @@ RetryDir:
         Return [Enum].GetName(EnumData.GetType, EnumData)
         Return [Enum].GetName(EnumData.GetType, EnumData)
     End Function
     End Function
     ''' <summary>
     ''' <summary>
+    ''' 将字符串转换为枚举类型,支持数字或原文两种格式,若为空字符串则返回 0。
+    ''' 若转换失败会抛出异常。
+    ''' </summary>
+    <Extension> Public Function ParseToEnum(Of T)(Str As String) As T
+        If String.IsNullOrEmpty(Str) Then
+            Return CType(0, Object)
+        ElseIf IsNumeric(Str) Then
+            Return CType(CInt(Str), Object)
+        Else
+            Return [Enum].Parse(GetType(T), Str, True)
+        End If
+    End Function
+    ''' <summary>
     ''' 将文件大小转化为适合的文本形式,如“1.28 M”。
     ''' 将文件大小转化为适合的文本形式,如“1.28 M”。
     ''' </summary>
     ''' </summary>
     ''' <param name="FileSize">以字节为单位的大小表示。</param>
     ''' <param name="FileSize">以字节为单位的大小表示。</param>
@@ -2579,7 +2592,7 @@ Retry:
                     Log(ex, "可能由于剪贴板被其他程序占用,文本复制失败", LogLevel.Hint)
                     Log(ex, "可能由于剪贴板被其他程序占用,文本复制失败", LogLevel.Hint)
                 End If
                 End If
             End Try
             End Try
-            If ShowSuccessHint Then Hint("已成功复制!", HintType.Finish)
+            If ShowSuccessHint Then Hint("已成功复制!", HintType.Green)
         End Sub)
         End Sub)
     End Sub
     End Sub
 
 
@@ -2716,6 +2729,11 @@ Retry:
     ''' 将 XML 转换为对应 UI 对象。
     ''' 将 XML 转换为对应 UI 对象。
     ''' </summary>
     ''' </summary>
     Public Function GetObjectFromXML(Str As String) As Object
     Public Function GetObjectFromXML(Str As String) As Object
+        Str = Str. '兼容旧版自定义事件写法
+            Replace("EventType=""", "local:CustomEventService.EventType=""").
+            Replace("EventData=""", "local:CustomEventService.EventData=""").
+            Replace("Property=""EventType""", "Property=""local:CustomEventService.EventType""").
+            Replace("Property=""EventData""", "Property=""local:CustomEventService.EventData""")
         Using Stream As New MemoryStream(Encoding.UTF8.GetBytes(Str))
         Using Stream As New MemoryStream(Encoding.UTF8.GetBytes(Str))
             '类型检查
             '类型检查
             Using Reader As New XamlXmlReader(Stream)
             Using Reader As New XamlXmlReader(Stream)
@@ -2831,7 +2849,7 @@ Retry:
                 File.Create(Path & "PCL\Log1.txt").Dispose()
                 File.Create(Path & "PCL\Log1.txt").Dispose()
             Catch ex As IOException
             Catch ex As IOException
                 IsInitSuccess = False
                 IsInitSuccess = False
-                Hint("可能同时开启了多个 PCL,程序可能会出现未知问题!", HintType.Critical)
+                Hint("可能同时开启了多个 PCL,程序可能会出现未知问题!", HintType.Red)
                 Log(ex, "日志初始化失败(疑似文件占用问题)")
                 Log(ex, "日志初始化失败(疑似文件占用问题)")
             Catch ex As Exception
             Catch ex As Exception
                 IsInitSuccess = False
                 IsInitSuccess = False
@@ -2884,6 +2902,7 @@ Retry:
 
 
         '输出日志
         '输出日志
         Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Text}{vbCrLf}" '减轻同步锁占用
         Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Text}{vbCrLf}" '减轻同步锁占用
+        AppendText = FilterUserName(FilterAccessToken(AppendText, "*"), "*")
         If ModeDebug Then
         If ModeDebug Then
             SyncLock LogListLock
             SyncLock LogListLock
                 LogList.Append(AppendText)
                 LogList.Append(AppendText)
@@ -2903,16 +2922,16 @@ Retry:
         Select Case Level
         Select Case Level
 #If DEBUG Then
 #If DEBUG Then
             Case LogLevel.Developer
             Case LogLevel.Developer
-                Hint("[开发者模式] " & Text, HintType.Info, False)
+                Hint("[开发者模式] " & Text, HintType.Blue, False)
             Case LogLevel.Debug
             Case LogLevel.Debug
-                Hint("[调试模式] " & Text, HintType.Info, False)
+                Hint("[调试模式] " & Text, HintType.Blue, False)
 #Else
 #Else
             Case LogLevel.Developer
             Case LogLevel.Developer
             Case LogLevel.Debug
             Case LogLevel.Debug
-                If ModeDebug Then Hint("[调试模式] " & Text, HintType.Info, False)
+                If ModeDebug Then Hint("[调试模式] " & Text, HintType.Blue, False)
 #End If
 #End If
             Case LogLevel.Hint
             Case LogLevel.Hint
-                Hint(Text, HintType.Critical, False)
+                Hint(Text, HintType.Red, False)
             Case LogLevel.Msgbox
             Case LogLevel.Msgbox
                 MyMsgBox(Text, Title, IsWarn:=True)
                 MyMsgBox(Text, Title, IsWarn:=True)
             Case LogLevel.Feedback
             Case LogLevel.Feedback
@@ -2955,6 +2974,7 @@ Retry:
 
 
         '输出日志
         '输出日志
         Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Desc}:{Ex.GetDetail(True)}{vbCrLf}" '减轻同步锁占用
         Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Desc}:{Ex.GetDetail(True)}{vbCrLf}" '减轻同步锁占用
+        AppendText = FilterUserName(FilterAccessToken(AppendText, "*"), "*")
         If ModeDebug Then
         If ModeDebug Then
             SyncLock LogListLock
             SyncLock LogListLock
                 LogList.Append(AppendText)
                 LogList.Append(AppendText)
@@ -2973,19 +2993,19 @@ Retry:
 #If DEBUG Then
 #If DEBUG Then
             Case LogLevel.Developer
             Case LogLevel.Developer
                 Dim ExLine As String = Desc & ":" & Ex.GetBrief()
                 Dim ExLine As String = Desc & ":" & Ex.GetBrief()
-                Hint("[开发者模式] " & ExLine, HintType.Info, False)
+                Hint("[开发者模式] " & ExLine, HintType.Blue, False)
             Case LogLevel.Debug
             Case LogLevel.Debug
                 Dim ExLine As String = Desc & ":" & Ex.GetBrief()
                 Dim ExLine As String = Desc & ":" & Ex.GetBrief()
-                Hint("[调试模式] " & ExLine, HintType.Info, False)
+                Hint("[调试模式] " & ExLine, HintType.Blue, False)
 #Else
 #Else
             Case LogLevel.Developer
             Case LogLevel.Developer
             Case LogLevel.Debug
             Case LogLevel.Debug
                 Dim ExLine As String = Desc & ":" & Ex.GetBrief()
                 Dim ExLine As String = Desc & ":" & Ex.GetBrief()
-                If ModeDebug Then Hint("[调试模式] " & ExLine, HintType.Info, False)
+                If ModeDebug Then Hint("[调试模式] " & ExLine, HintType.Blue, False)
 #End If
 #End If
             Case LogLevel.Hint
             Case LogLevel.Hint
                 Dim ExLine As String = Desc & ":" & Ex.GetBrief()
                 Dim ExLine As String = Desc & ":" & Ex.GetBrief()
-                Hint(ExLine, HintType.Critical, False)
+                Hint(ExLine, HintType.Red, False)
             Case LogLevel.Msgbox
             Case LogLevel.Msgbox
                 MyMsgBox(ExFull, Title, IsWarn:=True)
                 MyMsgBox(ExFull, Title, IsWarn:=True)
             Case LogLevel.Feedback
             Case LogLevel.Feedback

+ 21 - 19
Plain Craft Launcher 2/Modules/Base/ModNet.vb

@@ -998,20 +998,18 @@ StartThread:
                 If ModeDebug AndAlso Response.RequestMessage.RequestUri.ToString <> Th.Source.Url Then Log($"[Download] {LocalName}:重定向至 {Response.RequestMessage.RequestUri}")
                 If ModeDebug AndAlso Response.RequestMessage.RequestUri.ToString <> Th.Source.Url Then Log($"[Download] {LocalName}:重定向至 {Response.RequestMessage.RequestUri}")
                 '文件大小校验
                 '文件大小校验
                 ContentLength = Response.Content.Headers.ContentLength.GetValueOrDefault(-1)
                 ContentLength = Response.Content.Headers.ContentLength.GetValueOrDefault(-1)
-                If ContentLength = -1 Then
+                If ContentLength < 0 Then
                     If FileSize > 1 Then
                     If FileSize > 1 Then
                         If Th.DownloadStart = 0 Then
                         If Th.DownloadStart = 0 Then
                             Log($"[Download] {LocalName}:文件大小未知,但已从其他下载源获取,不作处理")
                             Log($"[Download] {LocalName}:文件大小未知,但已从其他下载源获取,不作处理")
                         Else
                         Else
-                            Log($"[Download] {LocalName}:ContentLength 返回了 -1,无法确定是否支持分段下载,视作不支持")
+                            Log($"[Download] {LocalName}:ContentLength 返回了 {ContentLength},无法确定是否支持分段下载,视作不支持")
                             GoTo NotSupportRange
                             GoTo NotSupportRange
                         End If
                         End If
                     Else
                     Else
                         FileSize = -1 : IsUnknownSize = True
                         FileSize = -1 : IsUnknownSize = True
                         Log($"[Download] {LocalName}:文件大小未知")
                         Log($"[Download] {LocalName}:文件大小未知")
                     End If
                     End If
-                ElseIf ContentLength < 0 Then
-                    Throw New Exception("获取片大小失败,结果为 " & ContentLength & "。")
                 ElseIf Th.IsFirstThread Then
                 ElseIf Th.IsFirstThread Then
                     If Check IsNot Nothing Then
                     If Check IsNot Nothing Then
                         If ContentLength < Check.MinSize AndAlso Check.MinSize > 0 Then
                         If ContentLength < Check.MinSize AndAlso Check.MinSize > 0 Then
@@ -1040,15 +1038,11 @@ StartThread:
                 ElseIf Th.DownloadStart > 0 AndAlso ContentLength = FileSize Then
                 ElseIf Th.DownloadStart > 0 AndAlso ContentLength = FileSize Then
 NotSupportRange:
 NotSupportRange:
                     SyncLock LockSource
                     SyncLock LockSource
-                        If SourcesOnce.Contains(Th.Source) Then
-                            GoTo SourceBreak
-                        Else
-                            SourcesOnce.Add(Th.Source)
-                        End If
+                        If SourcesOnce.Contains(Th.Source) Then GoTo SourceBreak
                     End SyncLock
                     End SyncLock
-                    Throw New WebException($"该下载源不支持分段下载:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}")
+                    Throw New RangeNotSupportedException($"该下载源不支持分段下载:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}")
                 ElseIf Not FileSize - Th.DownloadStart = ContentLength Then
                 ElseIf Not FileSize - Th.DownloadStart = ContentLength Then
-                    Throw New WebException($"获取到的分段大小不一致:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}")
+                    Throw New RangeNotSupportedException($"获取到的分段大小不一致:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}")
                 End If
                 End If
                 'Log($"[Download] {LocalName} {Info.Uuid}#:通过大小检查,文件大小 {FileSize},起始点 {Info.DownloadStart},ContentLength {ContentLength}")
                 'Log($"[Download] {LocalName} {Info.Uuid}#:通过大小检查,文件大小 {FileSize},起始点 {Info.DownloadStart},ContentLength {ContentLength}")
                 Th.State = NetState.Get
                 Th.State = NetState.Get
@@ -1061,6 +1055,7 @@ NotSupportRange:
                     Cache = New MemoryStream
                     Cache = New MemoryStream
                     ResultStream = Cache
                     ResultStream = Cache
                 Else
                 Else
+                    Directory.CreateDirectory(PathTemp & "Download")
                     Th.Temp = $"{PathTemp}Download\{Uuid}_{Th.Uuid}_{RandomInteger(0, 999999)}.tmp"
                     Th.Temp = $"{PathTemp}Download\{Uuid}_{Th.Uuid}_{RandomInteger(0, 999999)}.tmp"
                     ResultStream = New FileStream(Th.Temp, FileMode.Create, FileAccess.Write, FileShare.Read)
                     ResultStream = New FileStream(Th.Temp, FileMode.Create, FileAccess.Write, FileShare.Read)
                 End If
                 End If
@@ -1165,9 +1160,10 @@ SourceBreak:
             Th.State = NetState.Error
             Th.State = NetState.Error
             Th.Source.Ex = ex
             Th.Source.Ex = ex
             '根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传)
             '根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传)
-            If IsMergeFailure OrElse
-               ex.Message.Contains("该下载源不支持") OrElse ex.Message.Contains("未能解析") OrElse ex.Message.Contains("(404)") OrElse
-               ex.Message.Contains("(502)") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse ex.Message.Contains("获取到的分段大小不一致") OrElse
+            Dim IsRangeNotSupported As Boolean = TypeOf ex Is RangeNotSupportedException OrElse ex.Message.Contains("(416)")
+            If IsMergeFailure OrElse IsRangeNotSupported OrElse
+               ex.Message.Contains("(502)") OrElse ex.Message.Contains("(404)") OrElse
+               ex.Message.Contains("未能解析") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse
                ((ex.Message.Contains("(403)") OrElse ex.Message.Contains("(429)")) AndAlso Not Th.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403/429,所以不应因此禁用下载源
                ((ex.Message.Contains("(403)") OrElse ex.Message.Contains("(429)")) AndAlso Not Th.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403/429,所以不应因此禁用下载源
                (Th.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) AndAlso DownloadDone < 1) OrElse Th.Source.FailCount > NetTaskThreadLimit + 2 Then
                (Th.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) AndAlso DownloadDone < 1) OrElse Th.Source.FailCount > NetTaskThreadLimit + 2 Then
                 '当一个下载源有多个线程在下载时,只选择其中一个线程进行后续处理
                 '当一个下载源有多个线程在下载时,只选择其中一个线程进行后续处理
@@ -1180,9 +1176,9 @@ SourceBreak:
                 End SyncLock
                 End SyncLock
                 '……后续处理
                 '……后续处理
                 If IsThisFail Then
                 If IsThisFail Then
-                    Log($"[Download] {LocalName}:下载源被禁用({Th.Source.Id}):{Th.Source.Url}")
+                    Log($"[Download] {LocalName}:下载源被禁用({Th.Source.Id},Range 问题:{IsRangeNotSupported}):{Th.Source.Url}")
                     Log(ex, $"{If(SourcesOnce.FirstOrDefault?.SingleThread Is Nothing, "", "单线程")}下载源 {Th.Source.Id} 已被禁用",
                     Log(ex, $"{If(SourcesOnce.FirstOrDefault?.SingleThread Is Nothing, "", "单线程")}下载源 {Th.Source.Id} 已被禁用",
-                        If(ex.Message.Contains("不支持分段下载") OrElse ex.Message.Contains("(404)") OrElse ex.Message.Contains("(416)"), LogLevel.Developer, LogLevel.Debug))
+                        If(IsRangeNotSupported OrElse ex.Message.Contains("(404)"), LogLevel.Developer, LogLevel.Debug))
                     SyncLock LockSource
                     SyncLock LockSource
                         SourcesOnce.Remove(Th.Source)
                         SourcesOnce.Remove(Th.Source)
                     End SyncLock
                     End SyncLock
@@ -1191,9 +1187,10 @@ SourceBreak:
                         Fail(ex)
                         Fail(ex)
                     ElseIf HasAvailableSource() AndAlso Not IsMergeFailure Then
                     ElseIf HasAvailableSource() AndAlso Not IsMergeFailure Then
                         '当前源失败,但还有下载源:正常地继续执行
                         '当前源失败,但还有下载源:正常地继续执行
-                    ElseIf Not Retried AndAlso Sources.Count > 1 Then
+                    ElseIf Not Retried Then
                         '合并失败或首次下载失败,未重试:将所有下载源重新标记为不允许断点续传的下载源,逐个重新尝试下载
                         '合并失败或首次下载失败,未重试:将所有下载源重新标记为不允许断点续传的下载源,逐个重新尝试下载
-                        Hint($"{LocalName}:文件下载失败,正在自动重试……", If(ModeDebug, HintType.Critical, HintType.Info)) '在调试模式下给出红色提示
+                        '若所有源均不支持 Range,也会走到这里重试
+                        If Not IsRangeNotSupported Then Hint($"{LocalName}:文件下载失败,正在自动重试……", If(ModeDebug, HintType.Red, HintType.Blue)) '在调试模式下给出红色提示
                         Retried = True
                         Retried = True
                         SyncLock LockSource
                         SyncLock LockSource
                             SourcesOnce.Clear()
                             SourcesOnce.Clear()
@@ -1388,6 +1385,12 @@ Retry:
         End Sub
         End Sub
 
 
     End Class
     End Class
+    Private Class RangeNotSupportedException
+        Inherits WebException
+        Public Sub New(Message As String)
+            MyBase.New(Message)
+        End Sub
+    End Class
     ''' <summary>
     ''' <summary>
     ''' 下载一系列文件的加载器。
     ''' 下载一系列文件的加载器。
     ''' </summary>
     ''' </summary>
@@ -1890,7 +1893,6 @@ Retry:
                     Catch ex As Exception
                     Catch ex As Exception
                         Log(ex, "清理下载缓存失败")
                         Log(ex, "清理下载缓存失败")
                     End Try
                     End Try
-                    Directory.CreateDirectory(PathTemp & "Download")
                 End If
                 End If
                 IsDownloadCacheCleared = True
                 IsDownloadCacheCleared = True
             End SyncLock
             End SyncLock

+ 2 - 3
Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb

@@ -908,8 +908,7 @@ NextStack:
                         If File.Exists(OutputFile) Then
                         If File.Exists(OutputFile) Then
                             If FileEncoding Is Nothing Then FileEncoding = GetEncoding(ReadFileBytes(OutputFile))
                             If FileEncoding Is Nothing Then FileEncoding = GetEncoding(ReadFileBytes(OutputFile))
                             Dim FileContent As String = ReadFile(OutputFile, FileEncoding)
                             Dim FileContent As String = ReadFile(OutputFile, FileEncoding)
-                            FileContent = FilterAccessToken(FileContent, If(FileName = "启动脚本.bat", "F", "*"))
-                            FileContent = FilterUserName(FileContent, "*")
+                            FileContent = FilterUserName(FilterAccessToken(FileContent, If(FileName = "启动脚本.bat", "F", "*")), "*")
                             WriteFile(TempFolder & "Report\" & FileName, FileContent, Encoding:=FileEncoding)
                             WriteFile(TempFolder & "Report\" & FileName, FileContent, Encoding:=FileEncoding)
                             Log($"[Crash] 导出文件:{FileName},编码:{FileEncoding.HeaderName}")
                             Log($"[Crash] 导出文件:{FileName},编码:{FileEncoding.HeaderName}")
                         End If
                         End If
@@ -917,7 +916,7 @@ NextStack:
                     '导出报告
                     '导出报告
                     Compression.ZipFile.CreateFromDirectory(TempFolder & "Report\", FileAddress)
                     Compression.ZipFile.CreateFromDirectory(TempFolder & "Report\", FileAddress)
                     DeleteDirectory(TempFolder & "Report\")
                     DeleteDirectory(TempFolder & "Report\")
-                    Hint("错误报告已导出!", HintType.Finish)
+                    Hint("错误报告已导出!", HintType.Green)
                 Catch ex As Exception
                 Catch ex As Exception
                     Log(ex, "导出错误报告失败", LogLevel.Feedback)
                     Log(ex, "导出错误报告失败", LogLevel.Feedback)
                     Return
                     Return

+ 32 - 66
Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb

@@ -35,7 +35,7 @@ Public Module ModLaunch
         '预检查
         '预检查
         If Not RunInUi() Then Throw New Exception("McLaunchStart 必须在 UI 线程调用!")
         If Not RunInUi() Then Throw New Exception("McLaunchStart 必须在 UI 线程调用!")
         If McLaunchLoader.State = LoadState.Loading Then
         If McLaunchLoader.State = LoadState.Loading Then
-            Hint("已有游戏正在启动中!", HintType.Critical)
+            Hint("已有游戏正在启动中!", HintType.Red)
             Return False
             Return False
         End If
         End If
         '强制切换需要启动的版本
         '强制切换需要启动的版本
@@ -44,7 +44,7 @@ Public Module ModLaunch
             '检查版本
             '检查版本
             CurrentLaunchOptions.Version.Load()
             CurrentLaunchOptions.Version.Load()
             If CurrentLaunchOptions.Version.State = McVersionState.Error Then
             If CurrentLaunchOptions.Version.State = McVersionState.Error Then
-                Hint("无法启动 Minecraft:" & CurrentLaunchOptions.Version.Info, HintType.Critical)
+                Hint("无法启动 Minecraft:" & CurrentLaunchOptions.Version.Info, HintType.Red)
                 Return False
                 Return False
             End If
             End If
             '切换版本
             '切换版本
@@ -98,7 +98,7 @@ Public Module ModLaunch
             McLaunchPrecheck()
             McLaunchPrecheck()
             McLaunchLog("预检测已通过")
             McLaunchLog("预检测已通过")
         Catch ex As Exception
         Catch ex As Exception
-            If Not ex.Message.StartsWithF("$$") Then Hint(ex.Message, HintType.Critical)
+            If Not ex.Message.StartsWithF("$$") Then Hint(ex.Message, HintType.Red)
             Throw
             Throw
         End Try
         End Try
         Dim IsSavingBatch As Boolean = CurrentLaunchOptions?.SaveBatch IsNot Nothing
         Dim IsSavingBatch As Boolean = CurrentLaunchOptions?.SaveBatch IsNot Nothing
@@ -146,7 +146,7 @@ Public Module ModLaunch
             '成功与失败处理
             '成功与失败处理
             Select Case LaunchLoader.State
             Select Case LaunchLoader.State
                 Case LoadState.Finished
                 Case LoadState.Finished
-                    Hint(McVersionCurrent.Name & " 启动成功!", HintType.Finish)
+                    Hint(McVersionCurrent.Name & " 启动成功!", HintType.Green)
                     '上报
                     '上报
                     If Not IsSavingBatch Then
                     If Not IsSavingBatch Then
                         Telemetry("Minecraft 启动成功",
                         Telemetry("Minecraft 启动成功",
@@ -155,9 +155,9 @@ Public Module ModLaunch
                     End If
                     End If
                 Case LoadState.Aborted
                 Case LoadState.Aborted
                     If AbortHint Is Nothing Then
                     If AbortHint Is Nothing Then
-                        Hint(If(IsSavingBatch, "已取消导出启动脚本!", "已取消启动!"), HintType.Info)
+                        Hint(If(IsSavingBatch, "已取消导出启动脚本!", "已取消启动!"), HintType.Blue)
                     Else
                     Else
-                        Hint(AbortHint, HintType.Finish)
+                        Hint(AbortHint, HintType.Green)
                     End If
                     End If
                 Case LoadState.Failed
                 Case LoadState.Failed
                     Throw LaunchLoader.Error
                     Throw LaunchLoader.Error
@@ -270,7 +270,7 @@ NextInner:
                 Select Case MyMsgBox("你必须先登录正版账号,才能进行离线登录!", "正版验证", "购买正版", "试玩", "返回",
                 Select Case MyMsgBox("你必须先登录正版账号,才能进行离线登录!", "正版验证", "购买正版", "试玩", "返回",
                     Button1Action:=Sub() OpenWebsite("https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj"))
                     Button1Action:=Sub() OpenWebsite("https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj"))
                     Case 2
                     Case 2
-                        Hint("游戏将以试玩模式启动!", HintType.Critical)
+                        Hint("游戏将以试玩模式启动!", HintType.Red)
                         CurrentLaunchOptions.ExtraArgs.Add("--demo")
                         CurrentLaunchOptions.ExtraArgs.Add("--demo")
                     Case 3
                     Case 3
                         Throw New Exception("$$")
                         Throw New Exception("$$")
@@ -755,10 +755,10 @@ LoginFinish:
                 ContentType:="application/json; charset=utf-8", RequireJson:=True))
                 ContentType:="application/json; charset=utf-8", RequireJson:=True))
             '检查登录结果
             '检查登录结果
             If LoginJson("availableProfiles").Count = 0 Then
             If LoginJson("availableProfiles").Count = 0 Then
-                If Data.Input.ForceReselectProfile Then Hint("你还没有创建角色,无法更换!", HintType.Critical)
+                If Data.Input.ForceReselectProfile Then Hint("你还没有创建角色,无法更换!", HintType.Red)
                 Throw New Exception("$你还没有创建角色,请在创建角色后再试!")
                 Throw New Exception("$你还没有创建角色,请在创建角色后再试!")
             ElseIf Data.Input.ForceReselectProfile AndAlso LoginJson("availableProfiles").Count = 1 Then
             ElseIf Data.Input.ForceReselectProfile AndAlso LoginJson("availableProfiles").Count = 1 Then
-                Hint("你的账户中只有一个角色,无法更换!", HintType.Critical)
+                Hint("你的账户中只有一个角色,无法更换!", HintType.Red)
             End If
             End If
             Dim SelectedName As String = Nothing
             Dim SelectedName As String = Nothing
             Dim SelectedId As String = Nothing
             Dim SelectedId As String = Nothing
@@ -1304,7 +1304,7 @@ Retry:
             If McLaunchJavaSelected IsNot Nothing Then
             If McLaunchJavaSelected IsNot Nothing Then
                 McLaunchLog("选择的 Java:" & McLaunchJavaSelected.ToString)
                 McLaunchLog("选择的 Java:" & McLaunchJavaSelected.ToString)
             Else
             Else
-                Hint("没有可用的 Java,已取消启动!", HintType.Critical)
+                Hint("没有可用的 Java,已取消启动!", HintType.Red)
                 Throw New Exception("$$")
                 Throw New Exception("$$")
             End If
             End If
 
 
@@ -1395,7 +1395,7 @@ Retry:
         Next
         Next
         '自定义
         '自定义
         Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Version:=McVersionCurrent)
         Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Version:=McVersionCurrent)
-        Arguments += " " & If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame)
+        Arguments += " " & ArgumentReplace(If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame))
         '进行参数替换
         '进行参数替换
         Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader)
         Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader)
         If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then
         If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then
@@ -1412,6 +1412,9 @@ Retry:
             FinalArguments += Argument & " "
             FinalArguments += Argument & " "
         Next
         Next
         FinalArguments = FinalArguments.TrimEnd
         FinalArguments = FinalArguments.TrimEnd
+        '替换 Module Path 为短路径(Forge 会在版本 JSON 硬编码这些路径)
+        FinalArguments = FinalArguments.RegexReplaceEach("(?<= (-p|--module-path) "")[^""]+(?="" )",
+            Function(ModulePath) ModulePath.Value.RegexReplaceEach("[A-Z]{1}:\\[^;]+", Function(m) ShortenPath(m.Value)))
         '进服
         '进服
         Dim Server As String = If(String.IsNullOrEmpty(CurrentLaunchOptions.ServerIp), Setup.Get("VersionServerEnter", McVersionCurrent), CurrentLaunchOptions.ServerIp)
         Dim Server As String = If(String.IsNullOrEmpty(CurrentLaunchOptions.ServerIp), Setup.Get("VersionServerEnter", McVersionCurrent), CurrentLaunchOptions.ServerIp)
         If Server.Length > 0 Then
         If Server.Length > 0 Then
@@ -1427,7 +1430,7 @@ Retry:
                     '不包含端口号
                     '不包含端口号
                     FinalArguments += $" --server {Server} --port 25565"
                     FinalArguments += $" --server {Server} --port 25565"
                 End If
                 End If
-                If McVersionCurrent.Version.HasOptiFine Then Hint("OptiFine 与自动进入服务器可能不兼容,有概率导致材质丢失甚至游戏崩溃!", HintType.Critical)
+                If McVersionCurrent.Version.HasOptiFine Then Hint("OptiFine 与自动进入服务器可能不兼容,有概率导致材质丢失甚至游戏崩溃!", HintType.Red)
             End If
             End If
         End If
         End If
         '输出
         '输出
@@ -1447,7 +1450,7 @@ Retry:
         If ArgumentJvm = "" Then ArgumentJvm = Setup.Get("LaunchAdvanceJvm")
         If ArgumentJvm = "" Then ArgumentJvm = Setup.Get("LaunchAdvanceJvm")
         If Not ArgumentJvm.Contains("-Dlog4j2.formatMsgNoLookups=true") Then ArgumentJvm += " -Dlog4j2.formatMsgNoLookups=true"
         If Not ArgumentJvm.Contains("-Dlog4j2.formatMsgNoLookups=true") Then ArgumentJvm += " -Dlog4j2.formatMsgNoLookups=true"
         ArgumentJvm = ArgumentJvm.Replace(" -XX:MaxDirectMemorySize=256M", "") '#3511 的清理
         ArgumentJvm = ArgumentJvm.Replace(" -XX:MaxDirectMemorySize=256M", "") '#3511 的清理
-        DataList.Insert(0, ArgumentJvm) '可变 JVM 参数
+        DataList.Insert(0, ArgumentReplace(ArgumentJvm)) '可变 JVM 参数
         DataList.Add("-Xmn" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent, Not McLaunchJavaSelected.Is64Bit) * 1024 * 0.15) & "m")
         DataList.Add("-Xmn" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent, Not McLaunchJavaSelected.Is64Bit) * 1024 * 0.15) & "m")
         DataList.Add("-Xmx" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent, Not McLaunchJavaSelected.Is64Bit) * 1024) & "m")
         DataList.Add("-Xmx" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent, Not McLaunchJavaSelected.Is64Bit) * 1024) & "m")
         DataList.Add("""-Djava.library.path=" & GetNativesFolder() & """")
         DataList.Add("""-Djava.library.path=" & GetNativesFolder() & """")
@@ -1571,7 +1574,7 @@ NextVersion:
 
 
         '去重
         '去重
         DeDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M") '#3511 的清理
         DeDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M") '#3511 的清理
-        Dim Result As String = Join(DeDuplicateDataList.Distinct.ToList, " ")
+        Dim Result As String = DeDuplicateDataList.Distinct.Join(" ")
 
 
         '添加 MainClass
         '添加 MainClass
         If Version.JsonObject("mainClass") Is Nothing Then
         If Version.JsonObject("mainClass") Is Nothing Then
@@ -1592,7 +1595,7 @@ NextVersion:
         If Not BasicString.Contains("--height") Then BasicString += " --height ${resolution_height} --width ${resolution_width}"
         If Not BasicString.Contains("--height") Then BasicString += " --height ${resolution_height} --width ${resolution_width}"
         DataList.Add(BasicString)
         DataList.Add(BasicString)
 
 
-        Dim Result As String = Join(DataList, " ")
+        Dim Result As String = DataList.Join(" ")
 
 
         '特别改变 OptiFineTweaker
         '特别改变 OptiFineTweaker
         If (Version.Version.HasForge OrElse Version.Version.HasLiteLoader) AndAlso Version.Version.HasOptiFine Then
         If (Version.Version.HasForge OrElse Version.Version.HasLiteLoader) AndAlso Version.Version.HasOptiFine Then
@@ -1697,7 +1700,7 @@ NextVersion:
         GameArguments.Add("${launcher_version}", VersionCode)
         GameArguments.Add("${launcher_version}", VersionCode)
         GameArguments.Add("${version_name}", Version.Name)
         GameArguments.Add("${version_name}", Version.Name)
         Dim ArgumentInfo As String = Setup.Get("VersionArgumentInfo", Version:=McVersionCurrent)
         Dim ArgumentInfo As String = Setup.Get("VersionArgumentInfo", Version:=McVersionCurrent)
-        GameArguments.Add("${version_type}", If(ArgumentInfo = "", Setup.Get("LaunchArgumentInfo"), ArgumentInfo))
+        GameArguments.Add("${version_type}", ArgumentReplace(If(ArgumentInfo = "", Setup.Get("LaunchArgumentInfo"), ArgumentInfo)))
         GameArguments.Add("${game_directory}", ShortenPath(Left(McVersionCurrent.PathIndie, McVersionCurrent.PathIndie.Count - 1)))
         GameArguments.Add("${game_directory}", ShortenPath(Left(McVersionCurrent.PathIndie, McVersionCurrent.PathIndie.Count - 1)))
         GameArguments.Add("${assets_root}", ShortenPath(PathMcFolder & "assets"))
         GameArguments.Add("${assets_root}", ShortenPath(PathMcFolder & "assets"))
         GameArguments.Add("${user_properties}", "{}")
         GameArguments.Add("${user_properties}", "{}")
@@ -1707,7 +1710,7 @@ NextVersion:
         GameArguments.Add("${access_token}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${access_token}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${auth_session}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${auth_session}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${user_type}", "msa") '#1221
         GameArguments.Add("${user_type}", "msa") '#1221
-        GameArguments.Add("${primary_jar}", Version.Path & Version.Name & ".jar") '#6942
+        GameArguments.Add("${primary_jar}", ShortenPath(Version.Path & Version.Name & ".jar")) '#6942
 
 
         '窗口尺寸参数
         '窗口尺寸参数
         Dim GameSize As Size
         Dim GameSize As Size
@@ -1827,10 +1830,14 @@ NextVersion:
     ''' </summary>
     ''' </summary>
     Private Function GetNativesFolder() As String
     Private Function GetNativesFolder() As String
         Dim Result As String = McVersionCurrent.Path & McVersionCurrent.Name & "-natives"
         Dim Result As String = McVersionCurrent.Path & McVersionCurrent.Name & "-natives"
-        If IsGBKEncoding OrElse Result.IsASCII() Then Return ShortenPath(Result)
-        Result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\bin\natives"
-        If Result.IsASCII() Then Return ShortenPath(Result)
-        Return OsDrive & "ProgramData\PCL\natives"
+        If Not (IsGBKEncoding OrElse Result.IsASCII()) Then
+            Result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\bin\natives"
+            If Not Result.IsASCII() Then
+                Result = OsDrive & "ProgramData\PCL\natives"
+            End If
+        End If
+        Directory.CreateDirectory(Result) '提前创建,这样 ShortenPath 才有结果(否则在长路径下首次启动 Forge 会崩溃)
+        Return ShortenPath(Result)
     End Function
     End Function
 
 
 #End Region
 #End Region
@@ -1981,7 +1988,7 @@ NextVersion:
                McLoginLoader.Input.Type = McLoginType.Legacy AndAlso '离线登录
                McLoginLoader.Input.Type = McLoginType.Legacy AndAlso '离线登录
                (Setup.Get("LaunchSkinType") = 2 OrElse '强制 Alex
                (Setup.Get("LaunchSkinType") = 2 OrElse '强制 Alex
                (Setup.Get("LaunchSkinType") = 4 AndAlso Setup.Get("LaunchSkinSlim"))) Then '或选用 Alex 皮肤
                (Setup.Get("LaunchSkinType") = 4 AndAlso Setup.Get("LaunchSkinSlim"))) Then '或选用 Alex 皮肤
-            Hint("此 Minecraft 版本尚不支持 Alex 皮肤,你的皮肤可能会显示为 Steve!", HintType.Critical)
+            Hint("此 Minecraft 版本尚不支持 Alex 皮肤,你的皮肤可能会显示为 Steve!", HintType.Red)
         End If
         End If
 
 
         '离线皮肤资源包
         '离线皮肤资源包
@@ -2125,9 +2132,9 @@ IgnoreCustomSkin:
 
 
         '获取自定义命令
         '获取自定义命令
         Dim CustomCommandGlobal As String = Setup.Get("LaunchAdvanceRun")
         Dim CustomCommandGlobal As String = Setup.Get("LaunchAdvanceRun")
-        If CustomCommandGlobal <> "" Then CustomCommandGlobal = ArgumentReplace(CustomCommandGlobal, True)
+        If CustomCommandGlobal <> "" Then CustomCommandGlobal = ArgumentReplace(CustomCommandGlobal)
         Dim CustomCommandVersion As String = Setup.Get("VersionAdvanceRun", Version:=McVersionCurrent)
         Dim CustomCommandVersion As String = Setup.Get("VersionAdvanceRun", Version:=McVersionCurrent)
-        If CustomCommandVersion <> "" Then CustomCommandVersion = ArgumentReplace(CustomCommandVersion, True)
+        If CustomCommandVersion <> "" Then CustomCommandVersion = ArgumentReplace(CustomCommandVersion)
 
 
         '输出 bat
         '输出 bat
         Try
         Try
@@ -2283,7 +2290,7 @@ IgnoreCustomSkin:
         '获取窗口标题
         '获取窗口标题
         Dim WindowTitle As String = Setup.Get("VersionArgumentTitle", Version:=McVersionCurrent)
         Dim WindowTitle As String = Setup.Get("VersionArgumentTitle", Version:=McVersionCurrent)
         If WindowTitle = "" Then WindowTitle = Setup.Get("LaunchArgumentTitle")
         If WindowTitle = "" Then WindowTitle = Setup.Get("LaunchArgumentTitle")
-        WindowTitle = ArgumentReplace(WindowTitle, False)
+        WindowTitle = ArgumentReplace(WindowTitle, ReplaceTime:=False)
 
 
         '初始化等待
         '初始化等待
         Dim Watcher As New Watcher(Loader, McVersionCurrent, WindowTitle)
         Dim Watcher As New Watcher(Loader, McVersionCurrent, WindowTitle)
@@ -2332,47 +2339,6 @@ IgnoreCustomSkin:
 
 
     End Sub
     End Sub
 
 
-    ''' <summary>
-    ''' 在启动结束时,对 PCL 约定的替换标记进行处理。
-    ''' </summary>
-    Private Function ArgumentReplace(Raw As String, ReplaceTimeAndDate As Boolean) As String
-        If Raw Is Nothing Then Return Nothing
-        '路径替换
-        Raw = Raw.Replace("{minecraft}", PathMcFolder)
-        Raw = Raw.Replace("{verpath}", McVersionCurrent.Path)
-        Raw = Raw.Replace("{verindie}", McVersionCurrent.PathIndie)
-        Raw = Raw.Replace("{java}", McLaunchJavaSelected.PathFolder)
-        '普通替换
-        Raw = Raw.Replace("{user}", McLoginLoader.Output.Name)
-        Raw = Raw.Replace("{uuid}", McLoginLoader.Output.Uuid)
-        If ReplaceTimeAndDate Then '设置窗口标题时需要动态替换日期和时间
-            Raw = Raw.Replace("{date}", Date.Now.ToString("yyyy/M/d"))
-            Raw = Raw.Replace("{time}", Date.Now.ToString("HH:mm:ss"))
-        End If
-        Select Case McLoginLoader.Input.Type
-            Case McLoginType.Legacy
-                If PageLinkHiper.HiperState = LoadState.Finished Then
-                    Raw = Raw.Replace("{login}", "联机离线")
-                Else
-                    Raw = Raw.Replace("{login}", "离线")
-                End If
-            Case McLoginType.Ms
-                Raw = Raw.Replace("{login}", "正版")
-            Case McLoginType.Nide
-                Raw = Raw.Replace("{login}", "统一通行证")
-            Case McLoginType.Auth
-                Raw = Raw.Replace("{login}", "Authlib-Injector")
-        End Select
-        Raw = Raw.Replace("{name}", McVersionCurrent.Name)
-        If {"unknown", "old", "pending"}.Contains(McVersionCurrent.Version.McName.ToLower) Then
-            Raw = Raw.Replace("{version}", McVersionCurrent.Name)
-        Else
-            Raw = Raw.Replace("{version}", McVersionCurrent.Version.McName)
-        End If
-        Raw = Raw.Replace("{path}", Path)
-        Return Raw
-    End Function
-
 #End Region
 #End Region
 
 
 End Module
 End Module

+ 48 - 14
Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb

@@ -68,7 +68,7 @@ Public Module ModMinecraft
             For Each Folder As String In Setup.Get("LaunchFolders").Split("|")
             For Each Folder As String In Setup.Get("LaunchFolders").Split("|")
                 If Folder = "" Then Continue For
                 If Folder = "" Then Continue For
                 If Not Folder.Contains(">") OrElse Not Folder.EndsWithF("\") Then
                 If Not Folder.Contains(">") OrElse Not Folder.EndsWithF("\") Then
-                    Hint("无效的 Minecraft 文件夹:" & Folder, HintType.Critical)
+                    Hint("无效的 Minecraft 文件夹:" & Folder, HintType.Red)
                     Continue For
                     Continue For
                 End If
                 End If
                 Dim Name As String = Folder.Split(">")(0)
                 Dim Name As String = Folder.Split(">")(0)
@@ -261,6 +261,12 @@ Public Module ModMinecraft
         ''' 显示的描述文本。
         ''' 显示的描述文本。
         ''' </summary>
         ''' </summary>
         Public Info As String = "该版本未被加载,请向作者反馈此问题"
         Public Info As String = "该版本未被加载,请向作者反馈此问题"
+        Public ReadOnly Property HasCustomInfo As Boolean
+            Get
+                Return ReadIni(Path & "PCL\Setup.ini", "CustomInfo") <> ""
+            End Get
+        End Property
+
         ''' <summary>
         ''' <summary>
         ''' 该版本的列表检查原始结果,不受自定义影响。
         ''' 该版本的列表检查原始结果,不受自定义影响。
         ''' </summary>
         ''' </summary>
@@ -784,7 +790,17 @@ ExitDataLoad:
                 End If
                 End If
                 '确定版本描述
                 '确定版本描述
                 Dim CustomInfo As String = ReadIni(Path & "PCL\Setup.ini", "CustomInfo")
                 Dim CustomInfo As String = ReadIni(Path & "PCL\Setup.ini", "CustomInfo")
-                Info = If(CustomInfo <> "", CustomInfo, GetDefaultDescription())
+                If CustomInfo <> "" Then
+                    Info = CustomInfo
+                Else
+                    Info = GetVersionDescription()
+                    If State = McVersionState.Fool Then
+                        Info = GetMcFoolName(Version.McName)
+                    ElseIf State <> McVersionState.Error Then
+                        If Setup.Get("VersionServerLogin", Version:=Me) = 3 Then Info += ", 统一通行证验证"
+                        If Setup.Get("VersionServerLogin", Version:=Me) = 4 Then Info += ", Authlib 验证"
+                    End If
+                End If
                 '确定版本收藏状态
                 '确定版本收藏状态
                 IsStar = ReadIni(Path & "PCL\Setup.ini", "IsStar", False)
                 IsStar = ReadIni(Path & "PCL\Setup.ini", "IsStar", False)
                 '确定版本显示种类
                 '确定版本显示种类
@@ -817,11 +833,13 @@ ExitDataLoad:
             End Try
             End Try
             Return Me
             Return Me
         End Function
         End Function
+        Public IsLoaded As Boolean = False
         ''' <summary>
         ''' <summary>
-        ''' 获取版本的默认描述。
+        ''' 获取对该版本的简短描述。
+        ''' 例如 “快照 16w01a”、“原版 1.12.2”、“愚人节版本 2.0”。
         ''' </summary>
         ''' </summary>
-        Public Function GetDefaultDescription() As String
-            Dim Info As String = ""
+        Public Function GetVersionDescription() As String
+            Dim Info As String
             Select Case State
             Select Case State
                 Case McVersionState.Snapshot
                 Case McVersionState.Snapshot
                     If Version.McName.ContainsF("pre", True) Then
                     If Version.McName.ContainsF("pre", True) Then
@@ -838,21 +856,38 @@ ExitDataLoad:
                 Case McVersionState.Original, McVersionState.Forge, McVersionState.NeoForge, McVersionState.Fabric, McVersionState.OptiFine, McVersionState.LiteLoader
                 Case McVersionState.Original, McVersionState.Forge, McVersionState.NeoForge, McVersionState.Fabric, McVersionState.OptiFine, McVersionState.LiteLoader
                     Info = Version.ToString
                     Info = Version.ToString
                 Case McVersionState.Fool
                 Case McVersionState.Fool
-                    Info = GetMcFoolName(Version.McName)
+                    Info = "愚人节版本 " & Version.McName
                 Case McVersionState.Error
                 Case McVersionState.Error
                     Return Me.Info '已有错误信息
                     Return Me.Info '已有错误信息
                 Case Else
                 Case Else
                     Info = "发生了未知错误,请向作者反馈此问题"
                     Info = "发生了未知错误,请向作者反馈此问题"
             End Select
             End Select
-            If Not State = McVersionState.Error Then
-                If Setup.Get("VersionServerLogin", Version:=Me) = 3 Then Info += ", 统一通行证验证"
-                If Setup.Get("VersionServerLogin", Version:=Me) = 4 Then Info += ", Authlib 验证"
-            End If
             Return Info
             Return Info
         End Function
         End Function
 
 
-        Public IsLoaded As Boolean = False
+        Public Function ToListItem() As MyListItem
+            Dim NewItem As New MyListItem With {.Info = Info, .Height = 42, .Tag = Me, .SnapsToDevicePixels = True, .Type = MyListItem.CheckType.Clickable}
+            '标题
+            NewItem.Inlines.Clear()
+            NewItem.Inlines.Add(New Run(Name))
+            If HasCustomInfo Then '如果版本设置了自定义描述,在标题后面以淡灰色显示其版本号
+                NewItem.Inlines.Add(New Run("  |  " & GetVersionDescription()) With {.Foreground = New MyColor(215, 215, 215), .FontSize = 12})
+            End If
+            'Logo
+            Try
+                If Logo.EndsWith("PCL\Logo.png") Then
+                    NewItem.Logo = Path & "PCL\Logo.png" '修复老版本中,存储的自定义 Logo 使用完整路径,导致移动后无法加载的 Bug
+                Else
+                    NewItem.Logo = Logo
+                End If
+            Catch ex As Exception
+                Log(ex, "加载版本图标失败", LogLevel.Hint)
+                NewItem.Logo = "pack://application:,,,/images/Blocks/RedstoneBlock.png"
+            End Try
+            Return NewItem
+        End Function
 
 
+        '运算符支持
         Public Overrides Function Equals(obj As Object) As Boolean
         Public Overrides Function Equals(obj As Object) As Boolean
             Dim version = TryCast(obj, McVersion)
             Dim version = TryCast(obj, McVersion)
             Return version IsNot Nothing AndAlso Path = version.Path
             Return version IsNot Nothing AndAlso Path = version.Path
@@ -865,7 +900,6 @@ ExitDataLoad:
         Public Shared Operator <>(a As McVersion, b As McVersion) As Boolean
         Public Shared Operator <>(a As McVersion, b As McVersion) As Boolean
             Return Not (a = b)
             Return Not (a = b)
         End Operator
         End Operator
-
     End Class
     End Class
     Public Enum McVersionState
     Public Enum McVersionState
         [Error]
         [Error]
@@ -1567,12 +1601,12 @@ OnLoaded:
         Try
         Try
             Dim Image As New MyBitmap(FileName)
             Dim Image As New MyBitmap(FileName)
             If Image.Pic.Width <> 64 OrElse Not (Image.Pic.Height = 32 OrElse Image.Pic.Height = 64) Then
             If Image.Pic.Width <> 64 OrElse Not (Image.Pic.Height = 32 OrElse Image.Pic.Height = 64) Then
-                Hint("皮肤图片大小应为 64x32 像素或 64x64 像素!", HintType.Critical)
+                Hint("皮肤图片大小应为 64x32 像素或 64x64 像素!", HintType.Red)
                 Return New McSkinInfo With {.IsVaild = False}
                 Return New McSkinInfo With {.IsVaild = False}
             End If
             End If
             Dim FileInfo As New FileInfo(FileName)
             Dim FileInfo As New FileInfo(FileName)
             If FileInfo.Length > 24 * 1024 Then
             If FileInfo.Length > 24 * 1024 Then
-                Hint("皮肤文件大小需小于 24 KB,而所选文件大小为 " & Math.Round(FileInfo.Length / 1024, 2) & " KB", HintType.Critical)
+                Hint("皮肤文件大小需小于 24 KB,而所选文件大小为 " & Math.Round(FileInfo.Length / 1024, 2) & " KB", HintType.Red)
                 Return New McSkinInfo With {.IsVaild = False}
                 Return New McSkinInfo With {.IsVaild = False}
             End If
             End If
         Catch ex As Exception
         Catch ex As Exception

+ 12 - 12
Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb

@@ -31,7 +31,7 @@ Public Module ModModpack
         Try
         Try
             '字符校验
             '字符校验
             Dim TargetFolder As String = $"{PathMcFolder}versions\{VersionName}\"
             Dim TargetFolder As String = $"{PathMcFolder}versions\{VersionName}\"
-            If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("游戏路径中不能含有感叹号或分号:" & TargetFolder, HintType.Critical) : Throw New CancelledException
+            If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("游戏路径中不能含有感叹号或分号:" & TargetFolder, HintType.Red) : Throw New CancelledException
             '获取整合包种类与关键 Json
             '获取整合包种类与关键 Json
             Dim PackType As Integer = -1
             Dim PackType As Integer = -1
             Try
             Try
@@ -344,7 +344,7 @@ Retry:
         '重复任务检查
         '重复任务检查
         Dim LoaderName As String = "CurseForge 整合包安装:" & VersionName & " "
         Dim LoaderName As String = "CurseForge 整合包安装:" & VersionName & " "
         If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
         If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
-            Hint("该整合包正在安装中!", HintType.Critical)
+            Hint("该整合包正在安装中!", HintType.Red)
             Throw New CancelledException
             Throw New CancelledException
         End If
         End If
 
 
@@ -485,7 +485,7 @@ Retry:
         '重复任务检查
         '重复任务检查
         Dim LoaderName As String = $"Modrinth 整合包安装:{VersionName} "
         Dim LoaderName As String = $"Modrinth 整合包安装:{VersionName} "
         If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
         If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
-            Hint("该整合包正在安装中!", HintType.Critical)
+            Hint("该整合包正在安装中!", HintType.Red)
             Throw New CancelledException
             Throw New CancelledException
         End If
         End If
 
 
@@ -540,7 +540,7 @@ Retry:
         '重复任务检查
         '重复任务检查
         Dim LoaderName As String = "HMCL 整合包安装:" & VersionName & " "
         Dim LoaderName As String = "HMCL 整合包安装:" & VersionName & " "
         If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
         If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
-            Hint("该整合包正在安装中!", HintType.Critical)
+            Hint("该整合包正在安装中!", HintType.Red)
             Throw New CancelledException
             Throw New CancelledException
         End If
         End If
         '启动
         '启动
@@ -639,8 +639,8 @@ Retry:
         '构造版本安装请求
         '构造版本安装请求
         If PackJson("components") Is Nothing Then Throw New Exception("该 MMC 整合包未提供游戏版本信息,无法安装!")
         If PackJson("components") Is Nothing Then Throw New Exception("该 MMC 整合包未提供游戏版本信息,无法安装!")
         Dim Request As New McInstallRequest With {.TargetVersionName = VersionName, .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\"}
         Dim Request As New McInstallRequest With {.TargetVersionName = VersionName, .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\"}
-        For Each Component In PackJson("components")
-            If Not Component.Contains("uid") Then Continue For
+        For Each Component As JObject In PackJson("components")
+            If Not Component.ContainsKey("uid") Then Continue For
             Select Case Component("uid").ToString
             Select Case Component("uid").ToString
                 Case "org.lwjgl"
                 Case "org.lwjgl"
                     Log("[ModPack] 已跳过 LWJGL 项")
                     Log("[ModPack] 已跳过 LWJGL 项")
@@ -667,7 +667,7 @@ Retry:
         '重复任务检查
         '重复任务检查
         Dim LoaderName As String = "MMC 整合包安装:" & VersionName & " "
         Dim LoaderName As String = "MMC 整合包安装:" & VersionName & " "
         If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
         If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
-            Hint("该整合包正在安装中!", HintType.Critical)
+            Hint("该整合包正在安装中!", HintType.Red)
             Throw New CancelledException
             Throw New CancelledException
         End If
         End If
 
 
@@ -725,7 +725,7 @@ Retry:
         Next
         Next
         If Not Addons.ContainsKey("game") Then Throw New Exception("该 MCBBS 整合包未提供游戏版本信息,无法安装!")
         If Not Addons.ContainsKey("game") Then Throw New Exception("该 MCBBS 整合包未提供游戏版本信息,无法安装!")
         If Addons.ContainsKey("quilt") Then
         If Addons.ContainsKey("quilt") Then
-            Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical)
+            Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Red)
             Throw New CancelledException
             Throw New CancelledException
         End If
         End If
         Dim Request As New McInstallRequest With {
         Dim Request As New McInstallRequest With {
@@ -746,7 +746,7 @@ Retry:
         '重复任务检查
         '重复任务检查
         Dim LoaderName As String = "MCBBS 整合包安装:" & VersionName & " "
         Dim LoaderName As String = "MCBBS 整合包安装:" & VersionName & " "
         If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
         If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
-            Hint("该整合包正在安装中!", HintType.Critical)
+            Hint("该整合包正在安装中!", HintType.Red)
             Throw New CancelledException
             Throw New CancelledException
         End If
         End If
 
 
@@ -766,7 +766,7 @@ Retry:
         MyMsgBox("接下来请选择一个空文件夹,它会被安装到这个文件夹里。", "安装", "继续", ForceWait:=True)
         MyMsgBox("接下来请选择一个空文件夹,它会被安装到这个文件夹里。", "安装", "继续", ForceWait:=True)
         Dim TargetFolder As String = SelectFolder("选择安装目标(必须是一个空文件夹)")
         Dim TargetFolder As String = SelectFolder("选择安装目标(必须是一个空文件夹)")
         If String.IsNullOrEmpty(TargetFolder) Then Throw New CancelledException
         If String.IsNullOrEmpty(TargetFolder) Then Throw New CancelledException
-        If Directory.GetFileSystemEntries(TargetFolder).Length > 0 Then Hint("请选择一个空文件夹作为安装目标!", HintType.Critical) : Throw New CancelledException
+        If Directory.GetFileSystemEntries(TargetFolder).Length > 0 Then Hint("请选择一个空文件夹作为安装目标!", HintType.Red) : Throw New CancelledException
         '解压
         '解压
         Dim Loader As New LoaderCombo(Of String)("解压压缩包", {
         Dim Loader As New LoaderCombo(Of String)("解压压缩包", {
             New LoaderTask(Of String, Integer)("解压压缩包",
             New LoaderTask(Of String, Integer)("解压压缩包",
@@ -843,8 +843,8 @@ Retry:
         MyMsgBox("接下来请选择一个空文件夹,它会被安装到这个文件夹里。", "安装", "继续", ForceWait:=True)
         MyMsgBox("接下来请选择一个空文件夹,它会被安装到这个文件夹里。", "安装", "继续", ForceWait:=True)
         Dim TargetFolder As String = SelectFolder("选择安装目标(必须是一个空文件夹)")
         Dim TargetFolder As String = SelectFolder("选择安装目标(必须是一个空文件夹)")
         If String.IsNullOrEmpty(TargetFolder) Then Throw New CancelledException
         If String.IsNullOrEmpty(TargetFolder) Then Throw New CancelledException
-        If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Critical) : Throw New CancelledException
-        If Directory.GetFileSystemEntries(TargetFolder).Length > 0 Then Hint("请选择一个空文件夹作为安装目标!", HintType.Critical) : Throw New CancelledException
+        If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Red) : Throw New CancelledException
+        If Directory.GetFileSystemEntries(TargetFolder).Length > 0 Then Hint("请选择一个空文件夹作为安装目标!", HintType.Red) : Throw New CancelledException
         '解压
         '解压
         Dim Loader As New LoaderCombo(Of String)("解压压缩包", {
         Dim Loader As New LoaderCombo(Of String)("解压压缩包", {
             New LoaderTask(Of String, Integer)("解压压缩包",
             New LoaderTask(Of String, Integer)("解压压缩包",

+ 1 - 1
Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb

@@ -349,7 +349,7 @@
             State = MinecraftState.Crashed
             State = MinecraftState.Crashed
             '崩溃分析
             '崩溃分析
             WatcherLog("Minecraft 已崩溃,将在 2 秒后开始崩溃分析")
             WatcherLog("Minecraft 已崩溃,将在 2 秒后开始崩溃分析")
-            Hint("检测到 Minecraft 出现错误,错误分析已开始……", HintType.Critical)
+            Hint("检测到 Minecraft 出现错误,错误分析已开始……", HintType.Red)
             FeedbackInfo()
             FeedbackInfo()
             RunInNewThread(
             RunInNewThread(
             Sub()
             Sub()

+ 292 - 204
Plain Craft Launcher 2/Modules/ModEvent.vb

@@ -1,181 +1,291 @@
-Imports System.Collections.ObjectModel
+
+#Region "附加属性"
+
+''' <summary>
+''' 用于在 XAML 中初始化列表对象。
+''' 附加属性无法在 XAML 中为每个对象初始化独立的列表对象,因此需要一个包装类,然后在 XAML 中显式初始化。
+''' </summary>
+<Markup.ContentProperty("Events")>
+Public Class CustomEventCollection
+    Implements IEnumerable(Of CustomEvent)
+    Dim _Events As New List(Of CustomEvent)
+    Public ReadOnly Property Events As List(Of CustomEvent)
+        Get
+            Return _Events
+        End Get
+    End Property
+    Public Function GetEnumerator() As IEnumerator(Of CustomEvent) Implements IEnumerable(Of CustomEvent).GetEnumerator
+        Return DirectCast(Events, IEnumerable(Of CustomEvent)).GetEnumerator()
+    End Function
+    Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
+        Return DirectCast(Events, IEnumerable).GetEnumerator()
+    End Function
+End Class
+
+''' <summary>
+''' 提供自定义事件的附加属性。
+''' </summary>
+Public Class CustomEventService
+
+    'Events
+    Public Shared ReadOnly EventsProperty As DependencyProperty =
+            DependencyProperty.RegisterAttached("Events", GetType(CustomEventCollection), GetType(CustomEventService), New PropertyMetadata(Nothing))
+    <AttachedPropertyBrowsableForType(GetType(DependencyObject))>
+    Public Shared Sub SetEvents(d As DependencyObject, value As CustomEventCollection)
+        d.SetValue(EventsProperty, value)
+    End Sub
+    <AttachedPropertyBrowsableForType(GetType(DependencyObject))>
+    Public Shared Function GetEvents(d As DependencyObject) As CustomEventCollection
+        If d.GetValue(EventsProperty) Is Nothing Then d.SetValue(EventsProperty, New CustomEventCollection)
+        Return d.GetValue(EventsProperty)
+    End Function
+
+    'EventType
+    Public Shared ReadOnly EventTypeProperty As DependencyProperty =
+            DependencyProperty.RegisterAttached("EventType", GetType(CustomEvent.EventType), GetType(CustomEventService), New PropertyMetadata(Nothing))
+    <AttachedPropertyBrowsableForType(GetType(DependencyObject))>
+    Public Shared Sub SetEventType(d As DependencyObject, value As CustomEvent.EventType)
+        d.SetValue(EventTypeProperty, value)
+    End Sub
+    <AttachedPropertyBrowsableForType(GetType(DependencyObject))>
+    Public Shared Function GetEventType(d As DependencyObject) As CustomEvent.EventType
+        Return d.GetValue(EventTypeProperty)
+    End Function
+
+    'EventData
+    Public Shared ReadOnly EventDataProperty As DependencyProperty =
+            DependencyProperty.RegisterAttached("EventData", GetType(String), GetType(CustomEventService), New PropertyMetadata(Nothing))
+    <AttachedPropertyBrowsableForType(GetType(DependencyObject))>
+    Public Shared Sub SetEventData(d As DependencyObject, value As String)
+        d.SetValue(EventDataProperty, value)
+    End Sub
+    <AttachedPropertyBrowsableForType(GetType(DependencyObject))>
+    Public Shared Function GetEventData(d As DependencyObject) As String
+        Return d.GetValue(EventDataProperty)
+    End Function
+
+End Class
+
+Partial Public Module ModMain
 
 
-Public Module ModEvent
-#Region "事件执行方法"
     ''' <summary>
     ''' <summary>
-    ''' 供外部调用的自定义事件执行接口,会在新建工作线程中运行逻辑。
+    ''' 触发该控件上的自定义事件。
+    ''' 事件会在新线程中执行。
     ''' </summary>
     ''' </summary>
-    ''' <param name="SingleEventType">xaml中的EventType</param>
-    ''' <param name="SingleEventData">xaml中的EventData</param>
-    ''' <param name="EventCollection">xaml中的CustomEvents</param>
-    Public Sub ProcessCustomEvents(SingleEventType As String, SingleEventData As String, EventCollection As CustomEventCollection)
+    <Runtime.CompilerServices.Extension>
+    Public Sub RaiseCustomEvent(Control As DependencyObject)
+        '收集事件列表
+        Dim Events = CustomEventService.GetEvents(Control).ToList
+        Dim EventType = CustomEventService.GetEventType(Control)
+        If EventType <> CustomEvent.EventType.None Then Events.Add(New CustomEvent(EventType, CustomEventService.GetEventData(Control)))
+        '执行事件
+        If Not Events.Any Then Return
         RunInNewThread(
         RunInNewThread(
-            Sub()
-                Try
-                    '先执行单个事件
-                    StartCustomEvent(SingleEventType, SingleEventData)
-                    '再挨个执行事件集合中的事件
-                    If EventCollection IsNot Nothing Then
-                        For Each AnEvent In RunInUiWait(Function() EventCollection.DeepCloneToStructureList())
-                            StartCustomEvent(AnEvent.EventType, AnEvent.EventData)
-                        Next
-                    End If
-                Catch ex As Exception
-                    Log(ex, "自定义事件执行器发生异常", LogLevel.Feedback)
-                End Try
-            End Sub, "CustomEventProcessor " & GetUuid())
+        Sub()
+            For Each e In Events
+                e.Raise()
+            Next
+        End Sub, "执行自定义事件 " & GetUuid())
     End Sub
     End Sub
-    ''' <summary>
-    ''' 对当前线程阻塞地调用自定义事件处理器,接受来自 xaml 原始输入作为参数。
-    ''' </summary>
-    Private Sub StartCustomEvent(Type As String, DataRaw As String)
-        If String.IsNullOrEmpty(Type) Then Exit Sub
-        Dim Data = If(DataRaw Is Nothing, {""}, DataRaw.Split("|"))
-        Log("[Control] 执行自定义事件:" & Type & ", " & Join(Data, ", "))
-        '检测是否存在对应事件
-        If Not CustomEventProcessorsDict.ContainsKey(Type) Then
-            MyMsgBox($"未知的事件类型:'{Type}'{vbCrLf}请检查事件类型填写是否正确,或者 PCL 是否为最新版本。", "事件执行失败", ForceWait:=True)
-            Exit Sub
-        End If
-        '调用事件处理器
-        Try
-            CustomEventProcessorsDict(Type)(Data)
-        Catch ex As Exception
-            Log(ex, $"自定义事件执行失败:{Type}", LogLevel.Msgbox, "自定义主页事件执行失败")
-        End Try
+
+End Module
+
+#End Region
+
+''' <summary>
+''' 自定义事件。
+''' </summary>
+Public Class CustomEvent
+
+#Region "属性与触发"
+
+    Public Property Type As EventType = EventType.None
+    Public Property Data As String = Nothing
+    Public Sub New()
+    End Sub
+    Public Sub New(Type As EventType, Data As String)
+        Me.Type = Type
+        Me.Data = Data
     End Sub
     End Sub
 
 
     ''' <summary>
     ''' <summary>
-    ''' 存储各个自定义事件处理器的字典,以 EventType 为键,执行自定义事件时在工作线程中调用 Action 并传入分割后的 EventData 。
-    ''' Action 应当在事件逻辑结束后,即可以开始下一个事件时再结束;以便于挨个调用事件不打架。
+    ''' 在当前线程中触发该自定义事件。
     ''' </summary>
     ''' </summary>
-    Private ReadOnly CustomEventProcessorsDict As New Dictionary(Of String, Action(Of String())) From {
-        {"打开网页",
-        Sub(Data As String())
-            Data(0) = Data(0).Replace("\", "/")
-            If Not Data(0).Contains("://") OrElse Data(0).StartsWithF("file", True) Then '为了支持更多协议(#2200)
-                MyMsgBox("打开网页事件中,EventData 必须为一个网址。" & vbCrLf & "如果想要启动程序,请将 EventType 改为 打开文件。", "事件执行失败")
-                Exit Sub
-            End If
-            Hint("网页正在开启中,请稍候……")
-            OpenWebsite(Data(0))
-        End Sub},
-        {"打开文件",
-        Sub(Data As String())
-            Dim ActualPaths = GetEventAbsoluteUrls(Data(0), "打开文件")
-            Dim Location = ActualPaths(0), WorkingDir = ActualPaths(1)
-            Log($"[Control] 打开文件:路径:{Location},工作目录:{WorkingDir}")
-            If Not CommandRunUserConfirm(If(Data.Length < 2, Location, $"{Location} {Data(1)}")) Then Exit Sub
-            Dim Info As New ProcessStartInfo With {
-                .Arguments = If(Data.Length < 2, "", Data(1)),
-                .FileName = Location,
-                .WorkingDirectory = ShortenPath(WorkingDir)
-            }
-            Process.Start(Info)
-        End Sub},
-        {"打开帮助",
-        Sub(Data As String())
-            Dim ActualPaths = GetEventAbsoluteUrls(Data(0), "打开帮助")
-            Dim Location = ActualPaths(0)
-            Log($"[Control] 打开帮助:{Location}")
-            PageOtherHelp.EnterHelpPage(Location)
-        End Sub},
-        {"执行命令",
-        Sub(Data As String())
-            Dim ActualPaths = GetEventAbsoluteUrls(Data(0), "执行命令")
-            Dim Location = ActualPaths(0), WorkingDir = ActualPaths(1)
-            Log($"[Control] 执行命令:路径:{Location},工作目录:{WorkingDir}")
-            If Not CommandRunUserConfirm(If(Data.Length < 2, Location, $"{Location} {Data(1)}")) Then Exit Sub
-            Dim Info As New ProcessStartInfo With {
-                .Arguments = If(Data.Length < 2, "", Data(1)),
-                .FileName = Location,
-                .WorkingDirectory = ShortenPath(WorkingDir)
-            }
-            Process.Start(Info)
-        End Sub},
-        {"启动游戏",
-        Sub(Data As String())
-            If Data(0) = "\current" Then
-                If McVersionCurrent Is Nothing Then
-                    Hint("请先选择一个 Minecraft 版本!", HintType.Critical)
-                    Exit Sub
-                Else
-                    Data(0) = McVersionCurrent.Name
-                End If
-            End If
-            RunInUiWait(
-            Sub()
-                If McLaunchStart(New McLaunchOptions With {.Version = New McVersion(Data(0)), .ServerIp = If(Data.Length >= 2, Data(1), Nothing)}) Then
-                    Hint("正在启动 " & Data(0) & "……")
-                End If
-            End Sub)
-        End Sub},
-        {"复制文本", Sub(Data As String()) ClipboardSet(Join(Data, "|"))},
-        {"刷新主页",
-        Sub(Data As String())
-            RunInUiWait(Sub() FrmLaunchRight.ForceRefresh())
-            If Data(0) = "" Then Hint("已刷新主页!", HintType.Finish)
-        End Sub},
-        {"刷新帮助", Sub() RunInUi(Sub() PageOtherLeft.RefreshHelp())},
-        {"今日人品", Sub() PageOtherTest.Jrrp()},
-        {"内存优化",
-        Sub(Data As String())
-            If Data.Length >= 1 AndAlso Data(0) = "前台运行" Then
-                PageOtherTest.MemoryOptimize(Data.Length >= 2 AndAlso Data(1) = "显示提示")
-            Else
-                RunInNewThread(Sub() PageOtherTest.MemoryOptimize(True))
-            End If
-        End Sub},
-        {"弹出窗口", Sub(Data As String()) MyMsgBox(Data(1).Replace("\n", vbCrLf), Data(0).Replace("\n", vbCrLf), ForceWait:=True)},
-        {"切换页面", Sub(Data As String()) RunInUiWait(Sub() FrmMain.PageChange(Val(Data(0)), Val(Data(1))))},
-        {"安装整合包", Sub() RunInUiWait(Sub() ModpackInstall())},
-        {"导入整合包", Sub() RunInUiWait(Sub() ModpackInstall())},
-        {"下载文件",
-        Sub(Data As String())
-            Data(0) = Data(0).Replace("\", "/")
-            If Not (Data(0).StartsWithF("http://", True) OrElse Data(0).StartsWithF("https://", True)) Then
-                MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的下载协议。", "事件执行失败")
-                Exit Sub
-            End If
-            Try
-                Select Case Data.Length
-                    Case 1
-                        PageOtherTest.StartCustomDownload(Data(0), GetFileNameFromPath(Data(0)))
-                    Case 2
-                        PageOtherTest.StartCustomDownload(Data(0), Data(1))
-                    Case Else
-                        PageOtherTest.StartCustomDownload(Data(0), Data(1), Data(2))
-                End Select
-            Catch
-                PageOtherTest.StartCustomDownload(Data(0), "未知")
-            End Try
-        End Sub}
-    }
+    Public Sub Raise()
+        Raise(Type, Data)
+    End Sub
 
 
 #End Region
 #End Region
-#Region "工具方法"
+
+    Public Enum EventType
+        None = 0
+        打开网页
+        打开文件
+        打开帮助
+        执行命令
+        启动游戏
+        复制文本
+        刷新主页
+        刷新页面
+        刷新帮助
+        今日人品
+        内存优化
+        清理垃圾
+        弹出窗口
+        弹出提示
+        切换页面
+        导入整合包
+        安装整合包
+        下载文件
+        修改设置
+        写入设置
+        修改变量
+        写入变量
+    End Enum
+
     ''' <summary>
     ''' <summary>
-    ''' 弹窗询问用户是否同意继续执行打开类事件。
+    ''' 在当前线程中触发一个自定义事件。
     ''' </summary>
     ''' </summary>
-    Private Function CommandRunUserConfirm(TextShowToUser As String) As Boolean
-        If Setup.Get("HintCustomCommand") Then Return True
-        Select Case MyMsgBox("即将执行:" & TextShowToUser & vbCrLf & "请在确认该操作没有安全隐患后继续。", "执行确认", "继续", "继续且今后不再要求确认", "取消")
-            Case 1
-                Return True
-            Case 2
-                Setup.Set("HintCustomCommand", True)
-                Return True
-            Case Else '3
-                Log("[Control] 用户取消了打开类事件")
-                Return False
-        End Select
-    End Function
+    Public Shared Sub Raise(Type As EventType, Arg As String)
+        If Type = EventType.None Then Return
+        Log($"[Control] 执行自定义事件:{Type}, {Arg}")
+        Try
+            Dim Args As String() = If(Arg?.Split("|"), {""})
+            Select Case Type
+
+                Case EventType.打开网页
+                    Arg = Arg.Replace("\", "/")
+                    If Not Arg.Contains("://") OrElse Arg.StartsWithF("file", True) Then '为了支持更多协议(#2200)
+                        MyMsgBox("EventData 必须为一个网址。" & vbCrLf & "如果想要启动程序,请将 EventType 改为 打开文件。", "事件执行失败")
+                        Return
+                    End If
+                    Hint("正在开启中,请稍候:" & Arg)
+                    RunInThread(Sub() OpenWebsite(Arg))
+
+                Case EventType.打开文件, EventType.打开帮助, EventType.执行命令
+                    RunInThread(
+                    Sub()
+                        Try
+                            '确认实际路径
+                            Dim ActualPaths = GetAbsoluteUrls(Args(0), Type)
+                            Dim Location = ActualPaths(0), WorkingDir = ActualPaths(1)
+                            Log($"[Control] 打开类自定义事件实际路径:{Location},工作目录:{WorkingDir}")
+                            '执行
+                            If Type = EventType.打开帮助 Then
+                                PageOtherHelp.EnterHelpPage(Location)
+                            Else
+                                If Not EventSafetyConfirm("即将执行:" & Location & If(Args.Length >= 2, " " & Args(1), "")) Then Return
+                                Dim Info As New ProcessStartInfo With {
+                                    .Arguments = If(Args.Length >= 2, Args(1), ""),
+                                    .FileName = Location,
+                                    .WorkingDirectory = WorkingDir
+                                }
+                                StartProcess(Info)
+                            End If
+                        Catch ex As Exception
+                            Log(ex, "执行打开类自定义事件失败", LogLevel.Msgbox)
+                        End Try
+                    End Sub)
+
+                Case EventType.启动游戏
+                    If Args(0) = "\current" Then
+                        If McVersionCurrent Is Nothing Then
+                            Hint("请先选择一个 Minecraft 版本!", HintType.Red)
+                            Return
+                        Else
+                            Args(0) = McVersionCurrent.Name
+                        End If
+                    End If
+                    RunInUi(
+                    Sub()
+                        If McLaunchStart(New McLaunchOptions With
+                                {.ServerIp = If(Args.Length >= 2, Args(1), Nothing), .Version = New McVersion(Args(0))}) Then
+                            Hint($"正在启动 {Args(0)}……")
+                        End If
+                    End Sub)
+
+                Case EventType.复制文本
+                    ClipboardSet(Arg)
+
+                Case EventType.刷新主页, EventType.刷新页面
+                    If TypeOf FrmMain.PageRight Is IRefreshable Then
+                        RunInUiWait(Sub() CType(FrmMain.PageRight, IRefreshable).Refresh())
+                        If String.IsNullOrEmpty(Arg) Then Hint("已刷新!", HintType.Green)
+                    Else
+                        Hint("当前页面不支持刷新操作!", HintType.Red)
+                    End If
+
+                Case EventType.刷新帮助
+                    RunInUiWait(Sub() PageOtherLeft.RefreshHelp())
+                    If String.IsNullOrEmpty(Arg) Then Hint("已刷新!", HintType.Green)
+
+                Case EventType.今日人品
+                    PageOtherTest.Jrrp()
+
+                Case EventType.内存优化
+                    RunInThread(Sub() PageOtherTest.MemoryOptimize(True))
+
+                Case EventType.清理垃圾
+                    RunInThread(Sub() PageOtherTest.RubbishClear())
+
+                Case EventType.弹出窗口
+                    If Args.Length = 1 Then Throw New Exception($"EventType {Type} 需要至少 2 个以 | 分割的参数,例如 弹窗标题|弹窗内容")
+                    MyMsgBox(Args(1).Replace("\n", vbCrLf), Args(0).Replace("\n", vbCrLf), If(Args.Length > 2, Args(2), "确定"))
+
+                Case EventType.弹出提示
+                    Hint(Args(0).Replace("\n", vbCrLf), If(Args.Length = 1, HintType.Blue, Args(1).ParseToEnum(Of HintType)))
+
+                Case EventType.切换页面
+                    RunInUi(Sub() FrmMain.PageChange(
+                                Args(0).ParseToEnum(Of FormMain.PageType),
+                                If(Args.Length = 1, FormMain.PageSubType.Default, Args(1).ParseToEnum(Of FormMain.PageSubType))))
+
+                Case EventType.导入整合包, EventType.安装整合包
+                    RunInUi(Sub() ModpackInstall())
+
+                Case EventType.下载文件
+                    Args(0) = Args(0).Replace("\", "/")
+                    If Not (Args(0).StartsWithF("http://", True) OrElse Args(0).StartsWithF("https://", True)) Then
+                        MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的下载协议。", "事件执行失败")
+                        Return
+                    End If
+                    If Not EventSafetyConfirm("即将从该网址下载文件:" & vbCrLf & Args(0)) Then Return
+                    Try
+                        Select Case Args.Length
+                            Case 1
+                                PageOtherTest.StartCustomDownload(Args(0), GetFileNameFromPath(Args(0)))
+                            Case 2
+                                PageOtherTest.StartCustomDownload(Args(0), Args(1))
+                            Case Else
+                                PageOtherTest.StartCustomDownload(Args(0), Args(1), Args(2))
+                        End Select
+                    Catch
+                        PageOtherTest.StartCustomDownload(Args(0), "未知")
+                    End Try
+
+                Case EventType.修改设置, EventType.写入设置
+                    If Args.Length = 1 Then Throw New Exception($"EventType {Type} 需要至少 2 个以 | 分割的参数,例如 UiLauncherTransparent|400")
+                    Setup.SetSafe(Args(0), Args(1), Version:=McVersionCurrent)
+                    If Args.Length = 2 Then Hint($"已写入设置:{Args(0)} → {Args(1)}", HintType.Green)
+
+                Case EventType.修改变量, EventType.写入变量
+                    If Args.Length = 1 Then Throw New Exception($"EventType {Type} 需要至少 2 个以 | 分割的参数,例如 VariableName|SomeValue")
+                    WriteReg("CustomEvent" & Args(0), Args(1))
+                    If Args.Length = 2 Then Hint($"已写入变量:{Args(0)} → {Args(1)}", HintType.Green)
+
+                Case Else
+                    MyMsgBox("未知的事件类型:" & Type & vbCrLf & "请检查事件类型填写是否正确,或者 PCL 是否为最新版本。", "事件执行失败")
+            End Select
+        Catch ex As Exception
+            Log(ex, $"事件执行失败({Type}, {Arg})", LogLevel.Msgbox)
+        End Try
+    End Sub
 
 
     ''' <summary>
     ''' <summary>
     ''' 返回自定义事件的绝对 Url。实际返回 {绝对 Url, WorkingDir}。
     ''' 返回自定义事件的绝对 Url。实际返回 {绝对 Url, WorkingDir}。
     ''' 失败会抛出异常。
     ''' 失败会抛出异常。
     ''' </summary>
     ''' </summary>
-    Public Function GetEventAbsoluteUrls(RelativeUrl As String, EventType As String) As String()
+    Public Shared Function GetAbsoluteUrls(RelativeUrl As String, Type As EventType) As String()
 
 
         '网页确认
         '网页确认
         If RelativeUrl.StartsWithF("http", True) Then
         If RelativeUrl.StartsWithF("http", True) Then
@@ -216,25 +326,25 @@ Public Module ModEvent
         If RelativeUrl.Contains(":\") Then
         If RelativeUrl.Contains(":\") Then
             '绝对路径
             '绝对路径
             Location = RelativeUrl
             Location = RelativeUrl
-            Log("[Control] 自定义事件中由绝对路径" & EventType & ":" & Location)
+            Log("[Control] 自定义事件中由绝对路径" & Type & ":" & Location)
         ElseIf File.Exists(Path & "PCL\" & RelativeUrl) Then
         ElseIf File.Exists(Path & "PCL\" & RelativeUrl) Then
             '相对 PCL 文件夹的路径
             '相对 PCL 文件夹的路径
             Location = Path & "PCL\" & RelativeUrl
             Location = Path & "PCL\" & RelativeUrl
-            Log("[Control] 自定义事件中由相对 PCL 文件夹的路径" & EventType & ":" & Location)
+            Log("[Control] 自定义事件中由相对 PCL 文件夹的路径" & Type & ":" & Location)
         ElseIf File.Exists(Path & "PCL\Help\" & RelativeUrl) Then
         ElseIf File.Exists(Path & "PCL\Help\" & RelativeUrl) Then
             '相对 PCL 本地帮助文件夹的路径
             '相对 PCL 本地帮助文件夹的路径
             Location = Path & "PCL\Help\" & RelativeUrl
             Location = Path & "PCL\Help\" & RelativeUrl
             WorkingDir = Path & "PCL\Help\"
             WorkingDir = Path & "PCL\Help\"
-            Log("[Control] 自定义事件中由相对 PCL 本地帮助文件夹的路径" & EventType & ":" & Location)
-        ElseIf EventType = "打开帮助" AndAlso File.Exists(PathTemp & "Help\" & RelativeUrl) Then
+            Log("[Control] 自定义事件中由相对 PCL 本地帮助文件夹的路径" & Type & ":" & Location)
+        ElseIf Type = EventType.打开帮助 AndAlso File.Exists(PathTemp & "Help\" & RelativeUrl) Then
             '相对 PCL 自带帮助文件夹的路径
             '相对 PCL 自带帮助文件夹的路径
             Location = PathTemp & "Help\" & RelativeUrl
             Location = PathTemp & "Help\" & RelativeUrl
             WorkingDir = PathTemp & "Help\"
             WorkingDir = PathTemp & "Help\"
-            Log("[Control] 自定义事件中由相对 PCL 自带帮助文件夹的路径" & EventType & ":" & Location)
-        ElseIf EventType = "打开文件" OrElse EventType = "执行命令" Then
+            Log("[Control] 自定义事件中由相对 PCL 自带帮助文件夹的路径" & Type & ":" & Location)
+        ElseIf Type = EventType.打开文件 OrElse Type = EventType.执行命令 Then
             '直接使用原有路径启动程序
             '直接使用原有路径启动程序
             Location = RelativeUrl
             Location = RelativeUrl
-            Log("[Control] 自定义事件中直接" & EventType & ":" & Location)
+            Log("[Control] 自定义事件中直接" & Type & ":" & Location)
         Else
         Else
             '打开帮助,但是格式不对劲
             '打开帮助,但是格式不对劲
             Throw New FileNotFoundException("未找到 EventData 指向的本地 xaml 文件:" & RelativeUrl, RelativeUrl)
             Throw New FileNotFoundException("未找到 EventData 指向的本地 xaml 文件:" & RelativeUrl, RelativeUrl)
@@ -243,42 +353,20 @@ Public Module ModEvent
         Return {Location, WorkingDir}
         Return {Location, WorkingDir}
     End Function
     End Function
 
 
-#End Region
-End Module
-#Region "数据结构类声明"
-Public Class CustomEventCollection
-    Inherits ObservableCollection(Of CustomEvent)
-    Public Function DeepCloneToStructureList() As IList(Of CustomEventStructure)
-        DeepCloneToStructureList = New List(Of CustomEventStructure)
-        For Each AnEvent In Me
-            DeepCloneToStructureList.Add(New CustomEventStructure With {.EventType = AnEvent.EventType, .EventData = AnEvent.EventData})
-        Next
+    ''' <summary>
+    ''' 弹出安全确认弹窗。返回是否继续执行。
+    ''' </summary>
+    Private Shared Function EventSafetyConfirm(Message As String) As Boolean
+        If Setup.Get("HintCustomCommand") Then Return True
+        Select Case MyMsgBox(Message & vbCrLf & "请在确认没有安全隐患后再继续。", "执行确认", "继续", "继续且今后不再要求确认", "取消")
+            Case 1
+                Return True
+            Case 2
+                Setup.Set("HintCustomCommand", True)
+                Return True
+            Case Else
+                Return False
+        End Select
     End Function
     End Function
-End Class
-Public Class CustomEvent
-    Inherits DependencyObject
-    Public Property EventType As String
-        Get
-            Return GetValue(EventTypeProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventTypeProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(CustomEvent), New PropertyMetadata(Nothing))
-    Public Property EventData As String
-        Get
-            Return GetValue(EventDataProperty)
-        End Get
-        Set(value As String)
-            SetValue(EventDataProperty, value)
-        End Set
-    End Property
-    Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(CustomEvent), New PropertyMetadata(Nothing))
-End Class
-Public Structure CustomEventStructure
-    Public EventType As String
-    Public EventData As String
-End Structure
 
 
-#End Region
+End Class

+ 86 - 35
Plain Craft Launcher 2/Modules/ModMain.vb

@@ -7,23 +7,12 @@ Public Module ModMain
 
 
     ''' <summary>
     ''' <summary>
     ''' 提示信息的种类。
     ''' 提示信息的种类。
+    ''' 该枚举在自定义事件中使用,是公开 API 的一部分。
     ''' </summary>
     ''' </summary>
     Public Enum HintType
     Public Enum HintType
-        ''' <summary>
-        ''' 信息,通常是蓝色的“i”。
-        ''' </summary>
-        ''' <remarks></remarks>
-        Info
-        ''' <summary>
-        ''' 已完成,通常是绿色的“√”。
-        ''' </summary>
-        ''' <remarks></remarks>
-        Finish
-        ''' <summary>
-        ''' 错误,通常是红色的“×”。
-        ''' </summary>
-        ''' <remarks></remarks>
-        Critical
+        Blue
+        Green
+        Red
     End Enum
     End Enum
     Private Structure HintMessage
     Private Structure HintMessage
         Public Text As String
         Public Text As String
@@ -38,7 +27,7 @@ Public Module ModMain
     ''' <summary>
     ''' <summary>
     ''' 在窗口左下角弹出提示文本。
     ''' 在窗口左下角弹出提示文本。
     ''' </summary>
     ''' </summary>
-    Public Sub Hint(Text As String, Optional Type As HintType = HintType.Info, Optional Log As Boolean = True)
+    Public Sub Hint(Text As String, Optional Type As HintType = HintType.Blue, Optional Log As Boolean = True)
         If HintWaiting Is Nothing Then HintWaiting = New SafeList(Of HintMessage)
         If HintWaiting Is Nothing Then HintWaiting = New SafeList(Of HintMessage)
         HintWaiting.Add(New HintMessage With {.Text = If(Text, ""), .Type = Type, .Log = Log})
         HintWaiting.Add(New HintMessage With {.Text = If(Text, ""), .Type = Type, .Log = Log})
     End Sub
     End Sub
@@ -68,13 +57,13 @@ Public Module ModMain
                 Dim TargetColor0, TargetColor1 As MyColor
                 Dim TargetColor0, TargetColor1 As MyColor
                 Dim Percent As Double = 0.3
                 Dim Percent As Double = 0.3
                 Select Case CurrentHint.Type
                 Select Case CurrentHint.Type
-                    Case HintType.Info
+                    Case HintType.Blue
                         TargetColor0 = New MyColor(215, 37, 155, 252)
                         TargetColor0 = New MyColor(215, 37, 155, 252)
                         TargetColor1 = New MyColor(215, 10, 142, 252)
                         TargetColor1 = New MyColor(215, 10, 142, 252)
-                    Case HintType.Finish
+                    Case HintType.Green
                         TargetColor0 = New MyColor(215, 33, 177, 33)
                         TargetColor0 = New MyColor(215, 33, 177, 33)
                         TargetColor1 = New MyColor(215, 29, 160, 29)
                         TargetColor1 = New MyColor(215, 29, 160, 29)
-                    Case Else 'HintType.Critical
+                    Case Else 'HintType.Red
                         TargetColor0 = New MyColor(215, 255, 53, 11)
                         TargetColor0 = New MyColor(215, 255, 53, 11)
                         TargetColor1 = New MyColor(215, 255, 43, 0)
                         TargetColor1 = New MyColor(215, 255, 43, 0)
                 End Select
                 End Select
@@ -490,7 +479,7 @@ EndHint:
         ''' 是否为 “执行事件”。
         ''' 是否为 “执行事件”。
         ''' </summary>
         ''' </summary>
         Public IsEvent As Boolean
         Public IsEvent As Boolean
-        Public EventType As String
+        Public EventType As CustomEvent.EventType
         Public EventData As String
         Public EventData As String
         ''' <summary>
         ''' <summary>
         ''' 若非执行事件,其对应的 .xaml 本地文件内容。
         ''' 若非执行事件,其对应的 .xaml 本地文件内容。
@@ -504,7 +493,7 @@ EndHint:
         ''' </summary>
         ''' </summary>
         Public Sub New(FilePath As String)
         Public Sub New(FilePath As String)
             RawPath = FilePath
             RawPath = FilePath
-            Dim JsonData As JObject = GetJson(HelpArgumentReplace(ReadFile(FilePath)))
+            Dim JsonData As JObject = GetJson(ArgumentReplace(ReadFile(FilePath), AddressOf EscapeXML))
             If JsonData Is Nothing Then Throw New FileNotFoundException("未找到帮助文件:" & FilePath, FilePath)
             If JsonData Is Nothing Then Throw New FileNotFoundException("未找到帮助文件:" & FilePath, FilePath)
             '加载常规信息
             '加载常规信息
             If JsonData("Title") IsNot Nothing Then
             If JsonData("Title") IsNot Nothing Then
@@ -524,8 +513,7 @@ EndHint:
             Next
             Next
             '加载事件信息
             '加载事件信息
             If If(JsonData("IsEvent"), False) Then
             If If(JsonData("IsEvent"), False) Then
-                EventType = JsonData("EventType")
-                If EventType Is Nothing Then Throw New ArgumentException("未找到 EventType 项")
+                EventType = [Enum].Parse(GetType(CustomEvent.EventType), JsonData("EventType").ToString)
                 EventData = If(JsonData("EventData"), "")
                 EventData = If(JsonData("EventData"), "")
                 IsEvent = True
                 IsEvent = True
             Else
             Else
@@ -550,7 +538,7 @@ EndHint:
         Public Function SetToListItem(Item As MyListItem) As MyListItem
         Public Function SetToListItem(Item As MyListItem) As MyListItem
             Dim Logo As String
             Dim Logo As String
             If IsEvent Then
             If IsEvent Then
-                If EventType = "弹出窗口" Then
+                If EventType = CustomEvent.EventType.弹出窗口 Then
                     Logo = PathImage & "Blocks/GrassPath.png"
                     Logo = PathImage & "Blocks/GrassPath.png"
                 Else
                 Else
                     Logo = PathImage & "Blocks/CommandBlock.png"
                     Logo = PathImage & "Blocks/CommandBlock.png"
@@ -567,9 +555,9 @@ EndHint:
                 .Height = 42
                 .Height = 42
                 .Type = MyListItem.CheckType.Clickable
                 .Type = MyListItem.CheckType.Clickable
                 .Tag = Me
                 .Tag = Me
-                .EventType = Nothing
-                .EventData = Nothing
             End With
             End With
+            CustomEventService.SetEventType(Item, CustomEvent.EventType.None) '清空自定义事件属性,它们会被下面的点击事件处理
+            CustomEventService.SetEventData(Item, Nothing)
             '项目的点击事件
             '项目的点击事件
             AddHandler Item.Click, Sub(sender, e) PageOtherHelp.OnItemClick(sender.Tag)
             AddHandler Item.Click, Sub(sender, e) PageOtherHelp.OnItemClick(sender.Tag)
             Return Item
             Return Item
@@ -669,15 +657,6 @@ NextFile:
             Log("[Help] 已解压内置帮助文件,目前状态:" & File.Exists(PathTemp & "Help\启动器\备份设置.xaml"), LogLevel.Debug)
             Log("[Help] 已解压内置帮助文件,目前状态:" & File.Exists(PathTemp & "Help\启动器\备份设置.xaml"), LogLevel.Debug)
         End If
         End If
     End Sub
     End Sub
-    ''' <summary>
-    ''' 对帮助文件约定的替换标记进行处理,如果遇到需要转义的字符会进行转义。
-    ''' </summary>
-    Public Function HelpArgumentReplace(Xaml As String) As String
-        Dim Result = Xaml.Replace("{path}", EscapeXML(Path))
-        Result = Result.RegexReplaceEach("\{hint\}", Function() EscapeXML(PageOtherTest.GetRandomHint()))
-        Result = Result.RegexReplaceEach("\{cave\}", Function() EscapeXML(PageOtherTest.GetRandomCave()))
-        Return Result
-    End Function
 
 
 #End Region
 #End Region
 
 
@@ -836,6 +815,78 @@ NextFile:
         End Using
         End Using
     End Sub
     End Sub
 
 
+    ''' <summary>
+    ''' 对替换标记进行处理。会对替换内容使用 EscapeHandler 进行转义。
+    ''' </summary>
+    Public Function ArgumentReplace(Text As String, Optional EscapeHandler As Func(Of String, String) = Nothing, Optional ReplaceTime As Boolean = True) As String
+        '预处理
+        If Text Is Nothing Then Return Nothing
+        Dim Replacer =
+        Function(s As String) As String
+            If s Is Nothing Then Return ""
+            If EscapeHandler Is Nothing Then Return s
+            If s.Contains(":\") Then s = ShortenPath(s)
+            Return EscapeHandler(s)
+        End Function
+        '基础
+        Text = Text.Replace("{pcl_version}", Replacer(VersionBaseName))
+        Text = Text.Replace("{pcl_version_code}", Replacer(VersionCode))
+        Text = Text.Replace("{pcl_version_branch}", Replacer(VersionBranchName))
+        Text = Text.Replace("{pcl_branch}", Replacer(VersionBranchMain))
+        Text = Text.Replace("{identify}", Replacer(Identify))
+        Text = Text.Replace("{path}", Replacer(Path))
+        Text = Text.Replace("{path_with_name}", Replacer(PathWithName))
+        Text = Text.Replace("{path_temp}", Replacer(PathTemp))
+        '时间
+        If ReplaceTime Then '在窗口标题中,时间会被后续动态替换,所以此时不应该替换
+            Text = Text.Replace("{date}", Replacer(Date.Now.ToString("yyyy/M/d")))
+            Text = Text.Replace("{time}", Replacer(Date.Now.ToString("HH:mm:ss")))
+        End If
+        'Minecraft
+        Text = Text.Replace("{java}", Replacer(McLaunchJavaSelected?.PathFolder))
+        Text = Text.Replace("{minecraft}", Replacer(PathMcFolder))
+        If McVersionCurrent IsNot Nothing Then
+            Text = Text.Replace("{version_path}", Replacer(McVersionCurrent.Path)) : Text = Text.Replace("{verpath}", Replacer(McVersionCurrent.Path))
+            Text = Text.Replace("{version_indie}", Replacer(McVersionCurrent.PathIndie)) : Text = Text.Replace("{verindie}", Replacer(McVersionCurrent.PathIndie))
+            Text = Text.Replace("{name}", Replacer(McVersionCurrent.Name))
+            If {"unknown", "old", "pending"}.Contains(McVersionCurrent.Version.McName.ToLower) Then
+                Text = Text.Replace("{version}", Replacer(McVersionCurrent.Name))
+            Else
+                Text = Text.Replace("{version}", Replacer(McVersionCurrent.Version.McName))
+            End If
+        Else
+            Text = Text.Replace("{version_path}", Replacer(Nothing)) : Text = Text.Replace("{verpath}", Replacer(Nothing))
+            Text = Text.Replace("{version_indie}", Replacer(Nothing)) : Text = Text.Replace("{verindie}", Replacer(Nothing))
+            Text = Text.Replace("{name}", Replacer(Nothing))
+            Text = Text.Replace("{version}", Replacer(Nothing))
+        End If
+        '登录信息
+        If McLoginLoader.State = LoadState.Finished Then
+            Text = Text.Replace("{user}", Replacer(McLoginLoader.Output.Name))
+            Text = Text.Replace("{uuid}", Replacer(McLoginLoader.Output.Uuid.ToLower))
+            Select Case McLoginLoader.Input.Type
+                Case McLoginType.Legacy
+                    Text = Text.Replace("{login}", Replacer("离线"))
+                Case McLoginType.Ms
+                    Text = Text.Replace("{login}", Replacer("正版"))
+                Case McLoginType.Nide
+                    Text = Text.Replace("{login}", Replacer("统一通行证"))
+                Case McLoginType.Auth
+                    Text = Text.Replace("{login}", Replacer("Authlib-Injector"))
+            End Select
+        Else
+            Text = Text.Replace("{user}", Replacer(Nothing))
+            Text = Text.Replace("{uuid}", Replacer(Nothing))
+            Text = Text.Replace("{login}", Replacer(Nothing))
+        End If
+        '高级
+        Text = Text.RegexReplaceEach("\{hint\}", Function() Replacer(PageOtherTest.GetRandomHint()))
+        Text = Text.RegexReplaceEach("\{cave\}", Function() Replacer(PageOtherTest.GetRandomCave()))
+        Text = Text.RegexReplaceEach("\{setup:([a-zA-Z0-9]+)\}", Function(m) Replacer(Setup.GetSafe(m.Groups(1).Value, McVersionCurrent)))
+        Text = Text.RegexReplaceEach("\{varible:([^\}]+)\}", Function(m) Replacer(ReadReg("CustomEvent" & m.Groups(1).Value, Nothing)))
+        Return Text
+    End Function
+
 #End Region
 #End Region
 
 
 #Region "任务缓存"
 #Region "任务缓存"

+ 12 - 12
Plain Craft Launcher 2/Modules/ModMusic.vb

@@ -111,7 +111,7 @@
     ''' </summary>
     ''' </summary>
     Public Sub MusicControlPause()
     Public Sub MusicControlPause()
         If MusicNAudio Is Nothing Then
         If MusicNAudio Is Nothing Then
-            Hint("音乐播放尚未开始!", HintType.Critical)
+            Hint("音乐播放尚未开始!", HintType.Red)
         Else
         Else
             Select Case MusicState
             Select Case MusicState
                 Case MusicStates.Pause
                 Case MusicStates.Pause
@@ -131,14 +131,14 @@
     Public Sub MusicControlNext()
     Public Sub MusicControlNext()
         If MusicAllList.Count = 1 Then
         If MusicAllList.Count = 1 Then
             MusicStartPlay(MusicCurrent)
             MusicStartPlay(MusicCurrent)
-            Hint("重新播放:" & GetFileNameFromPath(MusicCurrent), HintType.Finish)
+            Hint("重新播放:" & GetFileNameFromPath(MusicCurrent), HintType.Green)
         Else
         Else
             Dim Address As String = DequeueNextMusicAddress()
             Dim Address As String = DequeueNextMusicAddress()
             If Address Is Nothing Then
             If Address Is Nothing Then
-                Hint("没有可以播放的音乐!", HintType.Critical)
+                Hint("没有可以播放的音乐!", HintType.Red)
             Else
             Else
                 MusicStartPlay(Address)
                 MusicStartPlay(Address)
-                Hint("正在播放:" & GetFileNameFromPath(Address), HintType.Finish)
+                Hint("正在播放:" & GetFileNameFromPath(Address), HintType.Green)
             End If
             End If
         End If
         End If
         MusicRefreshUI()
         MusicRefreshUI()
@@ -182,19 +182,19 @@
             MusicListInit(True)
             MusicListInit(True)
             If Not MusicAllList.Any() Then
             If Not MusicAllList.Any() Then
                 If MusicNAudio Is Nothing Then
                 If MusicNAudio Is Nothing Then
-                    If ShowHint Then Hint("未检测到可用的背景音乐!", HintType.Critical)
+                    If ShowHint Then Hint("未检测到可用的背景音乐!", HintType.Red)
                 Else
                 Else
                     MusicNAudio = Nothing
                     MusicNAudio = Nothing
-                    If ShowHint Then Hint("背景音乐已清除!", HintType.Finish)
+                    If ShowHint Then Hint("背景音乐已清除!", HintType.Green)
                 End If
                 End If
             Else
             Else
                 Dim Address As String = DequeueNextMusicAddress()
                 Dim Address As String = DequeueNextMusicAddress()
                 If Address Is Nothing Then
                 If Address Is Nothing Then
-                    If ShowHint Then Hint("没有可以播放的音乐!", HintType.Critical)
+                    If ShowHint Then Hint("没有可以播放的音乐!", HintType.Red)
                 Else
                 Else
                     Try
                     Try
                         MusicStartPlay(Address, IsFirstLoad)
                         MusicStartPlay(Address, IsFirstLoad)
-                        If ShowHint Then Hint("背景音乐已刷新:" & GetFileNameFromPath(Address), HintType.Finish, False)
+                        If ShowHint Then Hint("背景音乐已刷新:" & GetFileNameFromPath(Address), HintType.Green, False)
                     Catch
                     Catch
                     End Try
                     End Try
                 End If
                 End If
@@ -303,18 +303,18 @@
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "播放音乐出现内部错误(" & MusicCurrent & ")", LogLevel.Developer)
             Log(ex, "播放音乐出现内部错误(" & MusicCurrent & ")", LogLevel.Developer)
             If TypeOf ex Is NAudio.MmException AndAlso ex.Message.Contains("AlreadyAllocated") Then
             If TypeOf ex Is NAudio.MmException AndAlso ex.Message.Contains("AlreadyAllocated") Then
-                Hint("你的音频设备正被其他程序占用。请在关闭占用的程序后重启 PCL,才能恢复音乐播放功能!", HintType.Critical)
+                Hint("你的音频设备正被其他程序占用。请在关闭占用的程序后重启 PCL,才能恢复音乐播放功能!", HintType.Red)
                 Thread.Sleep(1000000000)
                 Thread.Sleep(1000000000)
             End If
             End If
             If TypeOf ex Is NAudio.MmException AndAlso (ex.Message.Contains("NoDriver") OrElse ex.Message.Contains("BadDeviceId")) Then
             If TypeOf ex Is NAudio.MmException AndAlso (ex.Message.Contains("NoDriver") OrElse ex.Message.Contains("BadDeviceId")) Then
-                Hint("由于音频设备变更,音乐播放功能在重启 PCL 后才能恢复!", HintType.Critical)
+                Hint("由于音频设备变更,音乐播放功能在重启 PCL 后才能恢复!", HintType.Red)
                 Thread.Sleep(1000000000)
                 Thread.Sleep(1000000000)
             End If
             End If
             If ex.Message.Contains("Got a frame at sample rate") OrElse ex.Message.Contains("does not support changes to") Then
             If ex.Message.Contains("Got a frame at sample rate") OrElse ex.Message.Contains("does not support changes to") Then
-                Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 不支持播放音频属性在中途发生变化的音乐", HintType.Critical)
+                Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 不支持播放音频属性在中途发生变化的音乐", HintType.Red)
             ElseIf Not (MusicCurrent.EndsWithF(".wav", True) OrElse MusicCurrent.EndsWithF(".mp3", True) OrElse MusicCurrent.EndsWithF(".flac", True)) OrElse
             ElseIf Not (MusicCurrent.EndsWithF(".wav", True) OrElse MusicCurrent.EndsWithF(".mp3", True) OrElse MusicCurrent.EndsWithF(".flac", True)) OrElse
                 ex.Message.Contains("0xC00D36C4") Then '#5096:不支持给定的 URL 的字节流类型。 (异常来自 HRESULT:0xC00D36C4)
                 ex.Message.Contains("0xC00D36C4") Then '#5096:不支持给定的 URL 的字节流类型。 (异常来自 HRESULT:0xC00D36C4)
-                Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 可能不支持此音乐格式,请将格式转换为 .wav、.mp3 或 .flac 后再试", HintType.Critical)
+                Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 可能不支持此音乐格式,请将格式转换为 .wav、.mp3 或 .flac 后再试", HintType.Red)
             Else
             Else
                 Log(ex, "播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & ")", LogLevel.Hint)
                 Log(ex, "播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & ")", LogLevel.Hint)
             End If
             End If

+ 4 - 2
Plain Craft Launcher 2/Modules/ModSecret.vb

@@ -6,6 +6,8 @@ Friend Module ModSecret
 
 
 #Region "杂项"
 #Region "杂项"
 
 
+    '标注 PCL 的不同分支,仅用于替换标记
+    Public Const VersionBranchMain As String = "OpenSource"
     '在开源版的注册表与常规版的注册表隔离,以防数据冲突
     '在开源版的注册表与常规版的注册表隔离,以防数据冲突
     Public Const RegFolder As String = "PCLDebug"
     Public Const RegFolder As String = "PCLDebug"
     '用于微软登录的 ClientId
     '用于微软登录的 ClientId
@@ -56,13 +58,13 @@ Friend Module ModSecret
     ''' <summary>
     ''' <summary>
     ''' 获取设备标识码。
     ''' 获取设备标识码。
     ''' </summary>
     ''' </summary>
-    Friend Function SecretGetUniqueAddress() As String
+    Friend Function SecretGetIdentify() As String
         Return "0000-0000-0000-0000"
         Return "0000-0000-0000-0000"
     End Function
     End Function
 
 
     Friend Sub SecretLaunchJvmArgs(ByRef DataList As List(Of String))
     Friend Sub SecretLaunchJvmArgs(ByRef DataList As List(Of String))
         Dim DataJvmCustom As String = Setup.Get("VersionAdvanceJvm", Version:=McVersionCurrent)
         Dim DataJvmCustom As String = Setup.Get("VersionAdvanceJvm", Version:=McVersionCurrent)
-        DataList.Insert(0, If(DataJvmCustom = "", Setup.Get("LaunchAdvanceJvm"), DataJvmCustom)) '可变 JVM 参数
+        DataList.Insert(0, ArgumentReplace(If(DataJvmCustom = "", Setup.Get("LaunchAdvanceJvm"), DataJvmCustom))) '可变 JVM 参数
         McLaunchLog("当前剩余内存:" & Math.Round(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024 / 1024 * 10) / 10 & "G")
         McLaunchLog("当前剩余内存:" & Math.Round(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024 / 1024 * 10) / 10 & "G")
         DataList.Add("-Xmn" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent) * 1024 * 0.15) & "m")
         DataList.Add("-Xmn" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent) * 1024 * 0.15) & "m")
         DataList.Add("-Xmx" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent) * 1024) & "m")
         DataList.Add("-Xmx" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent) * 1024) & "m")

+ 2 - 2
Plain Craft Launcher 2/My Project/AssemblyInfo.vb

@@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices
 ' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
 ' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
 ' 方法是按如下所示使用“*”
 ' 方法是按如下所示使用“*”
 
 
-<Assembly: AssemblyVersion("2.10.8.0")>
-<Assembly: AssemblyFileVersion("2.10.8.0")>
+<Assembly: AssemblyVersion("2.10.9.0")>
+<Assembly: AssemblyFileVersion("2.10.9.0")>
 <Assembly: NeutralResourcesLanguage("")>
 <Assembly: NeutralResourcesLanguage("")>

+ 7 - 7
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb

@@ -290,9 +290,9 @@
             Sub(MyLoader)
             Sub(MyLoader)
                 Select Case MyLoader.State
                 Select Case MyLoader.State
                     Case LoadState.Failed
                     Case LoadState.Failed
-                        Hint(MyLoader.Name & "失败:" & MyLoader.Error.GetBrief(), HintType.Critical)
+                        Hint(MyLoader.Name & "失败:" & MyLoader.Error.GetBrief(), HintType.Red)
                     Case LoadState.Aborted
                     Case LoadState.Aborted
-                        Hint(MyLoader.Name & "已取消!", HintType.Info)
+                        Hint(MyLoader.Name & "已取消!", HintType.Blue)
                     Case LoadState.Loading
                     Case LoadState.Loading
                         Return '不重新加载版本列表
                         Return '不重新加载版本列表
                 End Select
                 End Select
@@ -326,7 +326,7 @@
                 Dim DefaultFolder As String = Nothing
                 Dim DefaultFolder As String = Nothing
                 If File.Type <> CompType.ModPack Then
                 If File.Type <> CompType.ModPack Then
                     Dim SubFolder As String = Nothing
                     Dim SubFolder As String = Nothing
-                    Select Case Project.Type
+                    Select Case File.Type
                         Case CompType.Mod : SubFolder = "mods\"
                         Case CompType.Mod : SubFolder = "mods\"
                         Case CompType.ResourcePack : SubFolder = "resourcepacks\"
                         Case CompType.ResourcePack : SubFolder = "resourcepacks\"
                         Case CompType.Shader : SubFolder = "shaderpacks\"
                         Case CompType.Shader : SubFolder = "shaderpacks\"
@@ -346,7 +346,7 @@
                     Function(Version)
                     Function(Version)
                         If Not Version.IsLoaded Then Version.Load()
                         If Not Version.IsLoaded Then Version.Load()
                         '只对 Mod 和数据包进行版本检测
                         '只对 Mod 和数据包进行版本检测
-                        If Project.Type = CompType.Mod OrElse Project.Type = CompType.DataPack Then
+                        If File.Type = CompType.Mod OrElse File.Type = CompType.DataPack Then
                             If File.GameVersions.Any(Function(v) v.Contains(".")) AndAlso
                             If File.GameVersions.Any(Function(v) v.Contains(".")) AndAlso
                                Not File.GameVersions.Any(Function(v) v.Contains(".") AndAlso v = Version.Version.McName) Then Return False
                                Not File.GameVersions.Any(Function(v) v.Contains(".") AndAlso v = Version.Version.McName) Then Return False
                         End If
                         End If
@@ -359,8 +359,8 @@
                         Return False
                         Return False
                     End Function
                     End Function
                     '获取常规资源默认下载位置
                     '获取常规资源默认下载位置
-                    If CachedFolder.ContainsKey(Project.Type) Then
-                        DefaultFolder = CachedFolder(Project.Type)
+                    If CachedFolder.ContainsKey(File.Type) Then
+                        DefaultFolder = CachedFolder(File.Type)
                         Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{DefaultFolder}")
                         Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{DefaultFolder}")
                     ElseIf McVersionCurrent IsNot Nothing AndAlso IsVersionSuitable(McVersionCurrent) Then
                     ElseIf McVersionCurrent IsNot Nothing AndAlso IsVersionSuitable(McVersionCurrent) Then
                         DefaultFolder = $"{McVersionCurrent.PathIndie}{SubFolder}"
                         DefaultFolder = $"{McVersionCurrent.PathIndie}{SubFolder}"
@@ -425,7 +425,7 @@
                     If Not Target.Contains("\") Then Return
                     If Not Target.Contains("\") Then Return
                     '构造步骤加载器
                     '构造步骤加载器
                     Dim LoaderName As String = Desc & "下载:" & IO.Path.GetFileNameWithoutExtension(Target) & " "
                     Dim LoaderName As String = Desc & "下载:" & IO.Path.GetFileNameWithoutExtension(Target) & " "
-                    If Target <> DefaultFolder Then CachedFolder(Project.Type) = GetPathFromFullPath(Target)
+                    If Target <> DefaultFolder Then CachedFolder(File.Type) = GetPathFromFullPath(Target)
                     Dim Loaders As New List(Of LoaderBase)
                     Dim Loaders As New List(Of LoaderBase)
                     Loaders.Add(New LoaderDownload("下载文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 6, .Block = True})
                     Loaders.Add(New LoaderDownload("下载文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 6, .Block = True})
                     '启动
                     '启动

+ 18 - 18
Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb

@@ -32,7 +32,7 @@ Public Module ModDownloadLib
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For
                 If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For
                 If Behaviour = NetPreDownloadBehaviour.ExitWhileExistsOrDownloading Then Return OngoingLoader
                 If Behaviour = NetPreDownloadBehaviour.ExitWhileExistsOrDownloading Then Return OngoingLoader
-                Hint("该版本正在下载中!", HintType.Critical)
+                Hint("该版本正在下载中!", HintType.Red)
                 Return OngoingLoader
                 Return OngoingLoader
             Next
             Next
 
 
@@ -223,7 +223,7 @@ Public Module ModDownloadLib
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"Minecraft {Id} 服务端下载" Then Continue For
                 If OngoingLoader.Name <> $"Minecraft {Id} 服务端下载" Then Continue For
-                Hint("该服务端正在下载中!", HintType.Critical)
+                Hint("该服务端正在下载中!", HintType.Red)
                 Return
                 Return
             Next
             Next
 
 
@@ -241,7 +241,7 @@ Public Module ModDownloadLib
                         File.Delete(VersionFolder & Id & ".json")
                         File.Delete(VersionFolder & Id & ".json")
                         If Not New DirectoryInfo(VersionFolder).GetFileSystemInfos.Any() Then Directory.Delete(VersionFolder)
                         If Not New DirectoryInfo(VersionFolder).GetFileSystemInfos.Any() Then Directory.Delete(VersionFolder)
                         Task.Output = New List(Of NetFile)
                         Task.Output = New List(Of NetFile)
-                        Hint($"Mojang 没有给 Minecraft {Id} 提供官方服务端下载,没法下,撤退!", HintType.Critical)
+                        Hint($"Mojang 没有给 Minecraft {Id} 提供官方服务端下载,没法下,撤退!", HintType.Red)
                         Thread.Sleep(2000) '等玩家把上一个提示看完
                         Thread.Sleep(2000) '等玩家把上一个提示看完
                         Task.Abort()
                         Task.Abort()
                         Return
                         Return
@@ -300,7 +300,7 @@ pause"
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For
                 If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For
-                Hint("该版本正在下载中!", HintType.Critical)
+                Hint("该版本正在下载中!", HintType.Red)
                 Return
                 Return
             Next
             Next
 
 
@@ -401,7 +401,7 @@ pause"
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"OptiFine {DownloadInfo.NameDisplay} 下载" Then Continue For
                 If OngoingLoader.Name <> $"OptiFine {DownloadInfo.NameDisplay} 下载" Then Continue For
-                Hint("该版本正在下载中!", HintType.Critical)
+                Hint("该版本正在下载中!", HintType.Red)
                 Return
                 Return
             Next
             Next
 
 
@@ -832,7 +832,7 @@ Retry:
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For
                 If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For
-                Hint("该版本正在下载中!", HintType.Critical)
+                Hint("该版本正在下载中!", HintType.Red)
                 Return
                 Return
             Next
             Next
 
 
@@ -866,7 +866,7 @@ Retry:
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For
                 If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For
-                Hint("该版本正在下载中!", HintType.Critical)
+                Hint("该版本正在下载中!", HintType.Red)
                 Return
                 Return
             Next
             Next
 
 
@@ -1048,7 +1048,7 @@ Retry:
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"{DisplayName} 下载" Then Continue For
                 If OngoingLoader.Name <> $"{DisplayName} 下载" Then Continue For
-                Hint("该版本正在下载中!", HintType.Critical)
+                Hint("该版本正在下载中!", HintType.Red)
                 Return
                 Return
             Next
             Next
 
 
@@ -1803,7 +1803,7 @@ Retry:
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"Fabric {Version} 安装器下载" Then Continue For
                 If OngoingLoader.Name <> $"Fabric {Version} 安装器下载" Then Continue For
-                Hint("该版本正在下载中!", HintType.Critical)
+                Hint("该版本正在下载中!", HintType.Red)
                 Return
                 Return
             Next
             Next
 
 
@@ -1986,28 +1986,28 @@ Retry:
     ''' 在加载器状态改变后显示一条提示。
     ''' 在加载器状态改变后显示一条提示。
     ''' 不会进行任何其他操作。
     ''' 不会进行任何其他操作。
     ''' </summary>
     ''' </summary>
-    Public Sub LoaderStateChangedHintOnly(Loader)
+    Public Sub LoaderStateChangedHintOnly(Loader As LoaderBase)
         Select Case Loader.State
         Select Case Loader.State
             Case LoadState.Finished
             Case LoadState.Finished
-                Hint(Loader.Name & "成功!", HintType.Finish)
+                Hint(Loader.Name & "成功!", HintType.Green)
             Case LoadState.Failed
             Case LoadState.Failed
-                Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical)
+                Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Red)
             Case LoadState.Aborted
             Case LoadState.Aborted
-                Hint(Loader.Name & "已取消!", HintType.Info)
+                Hint(Loader.Name & "已取消!", HintType.Blue)
         End Select
         End Select
     End Sub
     End Sub
     ''' <summary>
     ''' <summary>
     ''' 安装加载器状态改变后进行提示和重载文件夹列表的方法。
     ''' 安装加载器状态改变后进行提示和重载文件夹列表的方法。
     ''' </summary>
     ''' </summary>
-    Public Sub McInstallState(Loader)
+    Public Sub McInstallState(Loader As LoaderBase)
         Select Case Loader.State
         Select Case Loader.State
             Case LoadState.Finished
             Case LoadState.Finished
                 WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") '清空缓存(合并安装会先生成文件夹,这会在刷新时误判为可以使用缓存)
                 WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") '清空缓存(合并安装会先生成文件夹,这会在刷新时误判为可以使用缓存)
-                Hint(Loader.Name & "成功!", HintType.Finish)
+                Hint(Loader.Name & "成功!", HintType.Green)
             Case LoadState.Failed
             Case LoadState.Failed
-                Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical)
+                Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Red)
             Case LoadState.Aborted
             Case LoadState.Aborted
-                Hint(Loader.Name & "已取消!", HintType.Info)
+                Hint(Loader.Name & "已取消!", HintType.Blue)
             Case LoadState.Loading
             Case LoadState.Loading
                 Return '不重新加载版本列表
                 Return '不重新加载版本列表
         End Select
         End Select
@@ -2108,7 +2108,7 @@ Retry:
 
 
         '重复版本检查
         '重复版本检查
         If File.Exists($"{VersionFolder}{Request.TargetVersionName}.json") Then
         If File.Exists($"{VersionFolder}{Request.TargetVersionName}.json") Then
-            Hint("版本 " & Request.TargetVersionName & " 已经存在!", HintType.Critical)
+            Hint("版本 " & Request.TargetVersionName & " 已经存在!", HintType.Red)
             Throw New CancelledException
             Throw New CancelledException
         End If
         End If
 
 

+ 11 - 11
Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml

@@ -1,8 +1,8 @@
 <Grid x:Class="MyMsgLogin"
 <Grid x:Class="MyMsgLogin"
-             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:my="clr-namespace:PCL"
-             RenderTransformOrigin="0,0.5" UseLayoutRounding="True" SnapsToDevicePixels="True" MinWidth="400" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="25">
+      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+      xmlns:local="clr-namespace:PCL"
+      RenderTransformOrigin="0,0.5" UseLayoutRounding="True" SnapsToDevicePixels="True" MinWidth="400" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="25">
     <Grid.RenderTransform>
     <Grid.RenderTransform>
         <TransformGroup>
         <TransformGroup>
             <RotateTransform x:Name="TransformRotate" Angle="-4" />
             <RotateTransform x:Name="TransformRotate" Angle="-4" />
@@ -23,17 +23,17 @@
             </Grid.RowDefinitions>
             </Grid.RowDefinitions>
             <TextBlock Grid.Row="0" FontSize="23" TextTrimming="None" Foreground="{DynamicResource ColorBrush2}" HorizontalAlignment="Left" Name="LabTitle" Margin="7,-1,70,9" Text="测试标题文本" VerticalAlignment="Top" SnapsToDevicePixels="False" UseLayoutRounding="False" />
             <TextBlock Grid.Row="0" FontSize="23" TextTrimming="None" Foreground="{DynamicResource ColorBrush2}" HorizontalAlignment="Left" Name="LabTitle" Margin="7,-1,70,9" Text="测试标题文本" VerticalAlignment="Top" SnapsToDevicePixels="False" UseLayoutRounding="False" />
             <Rectangle x:Name="ShapeLine" Grid.Row="1" Height="2" Fill="{Binding Foreground, ElementName=LabTitle}" />
             <Rectangle x:Name="ShapeLine" Grid.Row="1" Height="2" Fill="{Binding Foreground, ElementName=LabTitle}" />
-            <my:MyScrollViewer Grid.Row="3" VerticalAlignment="Top" x:Name="PanCaption" Margin="0,0,0,17" Padding="7,0,15,0"
+            <local:MyScrollViewer Grid.Row="3" VerticalAlignment="Top" x:Name="PanCaption" Margin="0,0,0,17" Padding="7,0,15,0"
                                VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" DeltaMult="0.7">
                                VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" DeltaMult="0.7">
                 <TextBlock VerticalAlignment="Top" TextTrimming="None" TextWrapping="Wrap" Name="LabCaption" FontSize="15" LineHeight="18"
                 <TextBlock VerticalAlignment="Top" TextTrimming="None" TextWrapping="Wrap" Name="LabCaption" FontSize="15" LineHeight="18"
                            Foreground="#FF5C5C5C" FontWeight="Normal" Padding="1" />
                            Foreground="#FF5C5C5C" FontWeight="Normal" Padding="1" />
-            </my:MyScrollViewer>
+            </local:MyScrollViewer>
             <StackPanel Grid.Row="4" Name="PanBtn" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="150,0,8,0" Orientation="Horizontal">
             <StackPanel Grid.Row="4" Name="PanBtn" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="150,0,8,0" Orientation="Horizontal">
-                <my:MyButton ColorType="Normal" x:FieldModifier="public" Text="重新打开网页" x:Name="Btn1" Margin="12,0,0,0" TextPadding="7" SnapsToDevicePixels="False" Padding="5,0" UseLayoutRounding="False"
-                             EventType="打开网页" />
-                <my:MyButton ColorType="Normal" x:FieldModifier="public" Text="复制代码" x:Name="Btn2" Margin="12,0,0,0" TextPadding="7" SnapsToDevicePixels="False" Padding="5,0" UseLayoutRounding="False"
-                             EventType="复制文本" />
-                <my:MyButton ColorType="Normal" x:FieldModifier="public" Text="取消" x:Name="Btn3" Margin="12,0,0,0" TextPadding="7" SnapsToDevicePixels="False" Padding="5,0" UseLayoutRounding="False" />
+                <local:MyButton ColorType="Normal" x:FieldModifier="public" Text="重新打开网页" x:Name="Btn1" Margin="12,0,0,0" TextPadding="7" SnapsToDevicePixels="False" Padding="5,0" UseLayoutRounding="False"
+                             local:CustomEventService.EventType="打开网页" />
+                <local:MyButton ColorType="Normal" x:FieldModifier="public" Text="复制代码" x:Name="Btn2" Margin="12,0,0,0" TextPadding="7" SnapsToDevicePixels="False" Padding="5,0" UseLayoutRounding="False"
+                             local:CustomEventService.EventType="复制文本" />
+                <local:MyButton ColorType="Normal" x:FieldModifier="public" Text="取消" x:Name="Btn3" Margin="12,0,0,0" TextPadding="7" SnapsToDevicePixels="False" Padding="5,0" UseLayoutRounding="False" />
             </StackPanel>
             </StackPanel>
         </Grid>
         </Grid>
     </Border>
     </Border>

+ 4 - 4
Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb

@@ -89,14 +89,14 @@
             $"登录网页将自动开启,请在网页中输入 {UserCode}(已自动复制)。" & vbCrLf & vbCrLf &
             $"登录网页将自动开启,请在网页中输入 {UserCode}(已自动复制)。" & vbCrLf & vbCrLf &
             $"如果网络环境不佳,网页可能一直加载不出来,届时请使用加速器或 VPN 改善网络环境。" & vbCrLf &
             $"如果网络环境不佳,网页可能一直加载不出来,届时请使用加速器或 VPN 改善网络环境。" & vbCrLf &
             $"你也可以用其他设备打开 {Website} 并输入上述代码。"
             $"你也可以用其他设备打开 {Website} 并输入上述代码。"
-        Btn1.EventData = Website
-        Btn2.EventData = UserCode
+        CustomEventService.SetEventData(Btn1, Website)
+        CustomEventService.SetEventData(Btn2, UserCode)
         '启动工作线程
         '启动工作线程
         RunInNewThread(AddressOf WorkThread, "MyMsgLogin")
         RunInNewThread(AddressOf WorkThread, "MyMsgLogin")
     End Sub
     End Sub
 
 
     Private Sub WorkThread()
     Private Sub WorkThread()
-        Thread.Sleep(3000)
+        Thread.Sleep(2000)
         If MyConverter.IsExited Then Return
         If MyConverter.IsExited Then Return
         OpenWebsite(Website)
         OpenWebsite(Website)
         ClipboardSet(UserCode)
         ClipboardSet(UserCode)
@@ -118,7 +118,7 @@
                 '获取结果
                 '获取结果
                 Dim ResultJson As JObject = GetJson(Result)
                 Dim ResultJson As JObject = GetJson(Result)
                 McLaunchLog($"令牌过期时间:{ResultJson("expires_in")} 秒")
                 McLaunchLog($"令牌过期时间:{ResultJson("expires_in")} 秒")
-                Hint("网页登录成功!", HintType.Finish)
+                Hint("网页登录成功!", HintType.Green)
                 Finished({ResultJson("access_token").ToString, ResultJson("refresh_token").ToString})
                 Finished({ResultJson("access_token").ToString, ResultJson("refresh_token").ToString})
                 Return
                 Return
             Catch ex As Exception
             Catch ex As Exception

+ 9 - 9
Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb

@@ -47,7 +47,7 @@
     Public Shared Sub Save(Loader As LoaderTask(Of EqualableList(Of String), String))
     Public Shared Sub Save(Loader As LoaderTask(Of EqualableList(Of String), String))
         Dim Address = Loader.Output
         Dim Address = Loader.Output
         If Not Loader.State = LoadState.Finished Then
         If Not Loader.State = LoadState.Finished Then
-            Hint("皮肤正在获取中,请稍候!", HintType.Critical)
+            Hint("皮肤正在获取中,请稍候!", HintType.Red)
             If Not Loader.State = LoadState.Loading Then Loader.Start()
             If Not Loader.State = LoadState.Loading Then Loader.Start()
             Return
             Return
         End If
         End If
@@ -61,7 +61,7 @@
                 Else
                 Else
                     CopyFile(Address, FileAddress)
                     CopyFile(Address, FileAddress)
                 End If
                 End If
-                Hint("皮肤保存成功!", HintType.Finish)
+                Hint("皮肤保存成功!", HintType.Green)
             End If
             End If
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "保存皮肤失败", LogLevel.Hint)
             Log(ex, "保存皮肤失败", LogLevel.Hint)
@@ -143,7 +143,7 @@
         Next
         Next
         If FrmLaunchLeft IsNot Nothing AndAlso HasLoaderRunning Then
         If FrmLaunchLeft IsNot Nothing AndAlso HasLoaderRunning Then
             '由于 Abort 不是实时的,暂时不会释放文件,会导致删除报错,故只能取消执行
             '由于 Abort 不是实时的,暂时不会释放文件,会导致删除报错,故只能取消执行
-            Hint("有正在获取中的皮肤,请稍后再试!", HintType.Info)
+            Hint("有正在获取中的皮肤,请稍后再试!", HintType.Blue)
         Else
         Else
             RunInThread(
             RunInThread(
             Sub()
             Sub()
@@ -161,7 +161,7 @@
                     For Each SkinLoader In If(sender IsNot Nothing, {sender}, {PageLaunchLeft.SkinLegacy, PageLaunchLeft.SkinMs})
                     For Each SkinLoader In If(sender IsNot Nothing, {sender}, {PageLaunchLeft.SkinLegacy, PageLaunchLeft.SkinMs})
                         SkinLoader.WaitForExit(IsForceRestart:=True)
                         SkinLoader.WaitForExit(IsForceRestart:=True)
                     Next
                     Next
-                    Hint("已刷新头像!", HintType.Finish)
+                    Hint("已刷新头像!", HintType.Green)
                 Catch ex As Exception
                 Catch ex As Exception
                     Log(ex, "刷新皮肤缓存失败", LogLevel.Msgbox)
                     Log(ex, "刷新皮肤缓存失败", LogLevel.Msgbox)
                 End Try
                 End Try
@@ -184,7 +184,7 @@
                     SkinLoader.WaitForExit(IsForceRestart:=True)
                     SkinLoader.WaitForExit(IsForceRestart:=True)
                 Next
                 Next
                 '完成提示
                 '完成提示
-                Hint("更改皮肤成功!", HintType.Finish)
+                Hint("更改皮肤成功!", HintType.Green)
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "更改正版皮肤后刷新皮肤失败", LogLevel.Feedback)
                 Log(ex, "更改正版皮肤后刷新皮肤失败", LogLevel.Feedback)
             End Try
             End Try
@@ -212,7 +212,7 @@
             Return
             Return
         End If
         End If
         If McLoginMsLoader.State = LoadState.Failed Then
         If McLoginMsLoader.State = LoadState.Failed Then
-            Hint("登录失败,无法更改披风!", HintType.Critical)
+            Hint("登录失败,无法更改披风!", HintType.Red)
             Return
             Return
         End If
         End If
         Hint("正在获取披风列表,请稍候……")
         Hint("正在获取披风列表,请稍候……")
@@ -225,7 +225,7 @@ Retry:
                 '获取登录信息
                 '获取登录信息
                 If McLoginMsLoader.State <> LoadState.Finished Then McLoginMsLoader.WaitForExit(PageLoginMsSkin.GetLoginData())
                 If McLoginMsLoader.State <> LoadState.Finished Then McLoginMsLoader.WaitForExit(PageLoginMsSkin.GetLoginData())
                 If McLoginMsLoader.State <> LoadState.Finished Then
                 If McLoginMsLoader.State <> LoadState.Finished Then
-                    Hint("登录失败,无法更改披风!", HintType.Critical)
+                    Hint("登录失败,无法更改披风!", HintType.Red)
                     Return
                     Return
                 End If
                 End If
                 Dim AccessToken As String = McLoginMsLoader.Output.AccessToken
                 Dim AccessToken As String = McLoginMsLoader.Output.AccessToken
@@ -265,10 +265,10 @@ Retry:
                     ContentType:="application/json",
                     ContentType:="application/json",
                     Headers:={{"Authorization", "Bearer " & AccessToken}})
                     Headers:={{"Authorization", "Bearer " & AccessToken}})
                 If Result.Contains("""errorMessage""") Then
                 If Result.Contains("""errorMessage""") Then
-                    Hint("更改披风失败:" & GetJson(Result)("errorMessage").ToString, HintType.Critical)
+                    Hint("更改披风失败:" & GetJson(Result)("errorMessage").ToString, HintType.Red)
                     Return
                     Return
                 Else
                 Else
-                    Hint("更改披风成功!", HintType.Finish)
+                    Hint("更改披风成功!", HintType.Green)
                 End If
                 End If
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "更改披风失败", LogLevel.Hint)
                 Log(ex, "更改披风失败", LogLevel.Hint)

+ 7 - 7
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb

@@ -134,6 +134,8 @@ Public Class PageLaunchRight
     Private Sub OnlineLoaderSub(Task As LoaderTask(Of String, Integer))
     Private Sub OnlineLoaderSub(Task As LoaderTask(Of String, Integer))
         Dim Address As String = Task.Input '#3721 中连续触发两次导致内容变化
         Dim Address As String = Task.Input '#3721 中连续触发两次导致内容变化
         Try
         Try
+            '替换自定义变量与设置
+            Address = ArgumentReplace(Address, AddressOf WebUtility.HtmlEncode)
             '获取版本校验地址
             '获取版本校验地址
             Dim VersionAddress As String
             Dim VersionAddress As String
             If Address.Contains(".xaml") Then
             If Address.Contains(".xaml") Then
@@ -146,29 +148,27 @@ Public Class PageLaunchRight
             End If
             End If
             '校验版本
             '校验版本
             Dim Version As String = ""
             Dim Version As String = ""
-            Dim NeedDownload As Boolean = True
             Try
             Try
                 Version = NetRequestByClientRetry(VersionAddress)
                 Version = NetRequestByClientRetry(VersionAddress)
                 If Version.Length > 1000 Then Throw New Exception($"获取的主页版本过长({Version.Length} 字符)")
                 If Version.Length > 1000 Then Throw New Exception($"获取的主页版本过长({Version.Length} 字符)")
                 Dim CurrentVersion As String = Setup.Get("CacheSavedPageVersion")
                 Dim CurrentVersion As String = Setup.Get("CacheSavedPageVersion")
                 If Version <> "" AndAlso CurrentVersion <> "" AndAlso Version = CurrentVersion Then
                 If Version <> "" AndAlso CurrentVersion <> "" AndAlso Version = CurrentVersion Then
                     Log($"[Page] 当前缓存的主页已为最新,当前版本:{Version},检查源:{VersionAddress}")
                     Log($"[Page] 当前缓存的主页已为最新,当前版本:{Version},检查源:{VersionAddress}")
-                    NeedDownload = False
-                Else
-                    Log($"[Page] 需要下载联网主页,当前版本:{Version},检查源:{VersionAddress}")
+                    Return
                 End If
                 End If
+                Log($"[Page] 需要下载联网主页,当前版本:{Version},检查源:{VersionAddress}")
             Catch exx As Exception
             Catch exx As Exception
                 Log(exx, $"联网获取主页版本失败", LogLevel.Developer)
                 Log(exx, $"联网获取主页版本失败", LogLevel.Developer)
                 Log($"[Page] 无法检查联网主页版本,将直接下载,检查源:{VersionAddress}")
                 Log($"[Page] 无法检查联网主页版本,将直接下载,检查源:{VersionAddress}")
             End Try
             End Try
             '实际下载
             '实际下载
-            If Not NeedDownload Then Return
             Dim FileContent As String = NetRequestByClientRetry(Address)
             Dim FileContent As String = NetRequestByClientRetry(Address)
             Log($"[Page] 已联网下载主页,内容长度:{FileContent.Length},来源:{Address}")
             Log($"[Page] 已联网下载主页,内容长度:{FileContent.Length},来源:{Address}")
             Setup.Set("CacheSavedPageUrl", Address)
             Setup.Set("CacheSavedPageUrl", Address)
             Setup.Set("CacheSavedPageVersion", Version)
             Setup.Set("CacheSavedPageVersion", Version)
             WriteFile(PathTemp & "Cache\Custom.xaml", FileContent)
             WriteFile(PathTemp & "Cache\Custom.xaml", FileContent)
-            Refresh() '要求刷新
+            '若内容变更则要求刷新
+            If LoadedContentHash <> FileContent.GetHashCode() Then Refresh()
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, $"下载主页失败({Address})", If(ModeDebug, LogLevel.Msgbox, LogLevel.Hint))
             Log(ex, $"下载主页失败({Address})", If(ModeDebug, LogLevel.Msgbox, LogLevel.Hint))
         End Try
         End Try
@@ -220,7 +220,7 @@ Public Class PageLaunchRight
                 End If
                 End If
                 Dim LoadStartTime As Date = Date.Now
                 Dim LoadStartTime As Date = Date.Now
                 '修改时应同时修改 PageOtherHelpDetail.Init
                 '修改时应同时修改 PageOtherHelpDetail.Init
-                Content = HelpArgumentReplace(Content)
+                Content = ArgumentReplace(Content, AddressOf EscapeXML)
                 Do While Content.Contains("xmlns")
                 Do While Content.Contains("xmlns")
                     Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间
                     Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间
                 Loop
                 Loop

+ 1 - 1
Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb

@@ -50,7 +50,7 @@
             If CType(McLoginLoader.Input, McLoginServer).ForceReselectProfile Then
             If CType(McLoginLoader.Input, McLoginServer).ForceReselectProfile Then
                 Hint("正在尝试更换,请稍候!")
                 Hint("正在尝试更换,请稍候!")
             Else
             Else
-                Hint("正在登录中,请稍后再更换角色!", HintType.Critical)
+                Hint("正在登录中,请稍后再更换角色!", HintType.Red)
             End If
             End If
             Return
             Return
         End If
         End If

+ 1 - 1
Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb

@@ -68,7 +68,7 @@ Public Class PageLoginLegacy
     End Sub
     End Sub
     Private Sub Skin_Click() Handles Skin.Click
     Private Sub Skin_Click() Handles Skin.Click
         If (Setup.Get("UiHiddenPageSetup") OrElse Setup.Get("UiHiddenSetupLaunch")) AndAlso Not PageSetupUI.HiddenForceShow Then
         If (Setup.Get("UiHiddenPageSetup") OrElse Setup.Get("UiHiddenSetupLaunch")) AndAlso Not PageSetupUI.HiddenForceShow Then
-            Hint("启动设置已被禁用!", HintType.Critical)
+            Hint("启动设置已被禁用!", HintType.Red)
         Else
         Else
             FrmMain.PageChange(FormMain.PageType.Setup, FormMain.PageSubType.SetupLaunch) '切换到皮肤设置页面
             FrmMain.PageChange(FormMain.PageType.Setup, FormMain.PageSubType.SetupLaunch) '切换到皮肤设置页面
         End If
         End If

+ 2 - 2
Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml

@@ -27,9 +27,9 @@
                 <ColumnDefinition Width="30" />
                 <ColumnDefinition Width="30" />
                 <ColumnDefinition Width="Auto" />
                 <ColumnDefinition Width="Auto" />
             </Grid.ColumnDefinitions>
             </Grid.ColumnDefinitions>
-            <local:MyTextButton Text="»  购买正版" EventType="打开网页" EventData="https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj"
+            <local:MyTextButton Text="»  购买正版" local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj"
                             Grid.Column="0" />
                             Grid.Column="0" />
-            <local:MyTextButton Text="»  前往官网" EventType="打开网页" EventData="https://www.minecraft.net/zh-hans"
+            <local:MyTextButton Text="»  前往官网" local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.minecraft.net/zh-hans"
                             Grid.Column="2" />
                             Grid.Column="2" />
         </Grid>
         </Grid>
     </Grid>
     </Grid>

+ 1 - 1
Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb

@@ -90,7 +90,7 @@
             Catch ex As Exception
             Catch ex As Exception
                 If ex.Message = "$$" Then
                 If ex.Message = "$$" Then
                 ElseIf ex.Message.StartsWith("$") Then
                 ElseIf ex.Message.StartsWith("$") Then
-                    Hint(ex.Message.TrimStart("$"), HintType.Critical)
+                    Hint(ex.Message.TrimStart("$"), HintType.Red)
                 ElseIf TypeOf ex Is Security.Authentication.AuthenticationException AndAlso ex.Message.ContainsF("SSL/TLS") Then
                 ElseIf TypeOf ex Is Security.Authentication.AuthenticationException AndAlso ex.Message.ContainsF("SSL/TLS") Then
                     Log(ex, "正版登录验证失败,请考虑在 [设置 → 其他] 中关闭 [在正版登录时验证 SSL 证书],然后再试。" & vbCrLf & vbCrLf & "原始错误信息:", LogLevel.Msgbox)
                     Log(ex, "正版登录验证失败,请考虑在 [设置 → 其他] 中关闭 [在正版登录时验证 SSL 证书],然后再试。" & vbCrLf & vbCrLf & "原始错误信息:", LogLevel.Msgbox)
                 Else
                 Else

+ 3 - 3
Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb

@@ -77,7 +77,7 @@
             Return
             Return
         End If
         End If
         If McLoginLoader.State = LoadState.Failed Then
         If McLoginLoader.State = LoadState.Failed Then
-            Hint("登录失败,无法更改皮肤!", HintType.Critical)
+            Hint("登录失败,无法更改皮肤!", HintType.Red)
             Return
             Return
         End If
         End If
         Dim SkinInfo As McSkinInfo = McSkinSelect()
         Dim SkinInfo As McSkinInfo = McSkinSelect()
@@ -106,7 +106,7 @@ Retry:
                     McLoginMsLoader.Start(GetLoginData(), IsForceRestart:=True)
                     McLoginMsLoader.Start(GetLoginData(), IsForceRestart:=True)
                     GoTo Retry
                     GoTo Retry
                 ElseIf Result.Contains("""error""") Then
                 ElseIf Result.Contains("""error""") Then
-                    Hint("更改皮肤失败:" & GetJson(Result)("error").ToString, HintType.Critical)
+                    Hint("更改皮肤失败:" & GetJson(Result)("error").ToString, HintType.Red)
                     Return
                     Return
                 End If
                 End If
                 '获取新皮肤地址
                 '获取新皮肤地址
@@ -122,7 +122,7 @@ Retry:
                 Throw New Exception("未知错误(" & Result & ")")
                 Throw New Exception("未知错误(" & Result & ")")
             Catch ex As Exception
             Catch ex As Exception
                 If TypeOf ex Is OperationCanceledException Then
                 If TypeOf ex Is OperationCanceledException Then
-                    Hint("更改皮肤失败:连接皮肤服务器超时,请稍后再试,或使用 VPN 改善网络环境", HintType.Critical)
+                    Hint("更改皮肤失败:连接皮肤服务器超时,请稍后再试,或使用 VPN 改善网络环境", HintType.Red)
                 Else
                 Else
                     Log(ex, "更改皮肤失败", LogLevel.Hint)
                     Log(ex, "更改皮肤失败", LogLevel.Hint)
                 End If
                 End If

+ 0 - 19
Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml

@@ -8,29 +8,10 @@
         <StackPanel Name="PanMain" Margin="25,25,25,10" Grid.IsSharedSizeScope="True">
         <StackPanel Name="PanMain" Margin="25,25,25,10" Grid.IsSharedSizeScope="True">
             <local:MyCard Title="关于" Margin="0,0,0,15">
             <local:MyCard Title="关于" Margin="0,0,0,15">
                 <StackPanel Margin="20,40,18,18">
                 <StackPanel Margin="20,40,18,18">
-                    <TextBlock TextWrapping="Wrap" Text="多人联机服务由速聚授权提供,PCL 仅为速聚提供多人联机服务入口,使用中遇到的任何问题由速聚负责处理。你在使用多人联机服务过程中所遇到的任何问题与纠纷(包括其付费业务)均与 PCL 无关,应与速聚协商解决。" 
-                           Margin="0,0,0,3" />
-                    <TextBlock TextWrapping="Wrap" Text="PCL 作者并不会从速聚的付费业务中获得任何分成、奖金等报酬,请不要瞎说。" 
-                           Margin="0,0,0,6" />
-                    <local:MyListItem Title="速聚官网" Info="打开速聚的官方网站"
-                              Type="Clickable" EventType="打开网页" EventData="https://www.the.bb/zh-CN.html"
-                              Logo="pack://application:,,,/images/Blocks/Grass.png" />
-                    <local:MyListItem Title="授权协议书" Info="查看速聚与 PCL 间的授权协议书"
-                              Type="Clickable" EventType="打开网页" EventData="https://shimo.im/docs/0l3NVXYzeXhb903R"
-                              Logo="pack://application:,,,/images/Blocks/Grass.png" />
-                    <local:MyListItem Title="用户协议与隐私政策" Info="查看速聚的用户协议与隐私政策文档"
-                              Type="Clickable" EventType="打开网页" EventData="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzkxMTMyODk3Mg==&amp;action=getalbum&amp;album_id=2585385685407514625&amp;scene=173&amp;from_msgid=2247483720&amp;from_itemidx=1&amp;count=3&amp;nolastread=1#wechat_redirect"
-                              Logo="pack://application:,,,/images/Blocks/Grass.png" />
                 </StackPanel>
                 </StackPanel>
             </local:MyCard>
             </local:MyCard>
             <local:MyCard Title="发送反馈" Margin="0,0,0,15">
             <local:MyCard Title="发送反馈" Margin="0,0,0,15">
                 <StackPanel Margin="20,40,18,18">
                 <StackPanel Margin="20,40,18,18">
-                    <local:MyListItem Title="联机反馈与交流" Info="加入联机模块官方 QQ 群反馈联机异常,或询问联机功能的使用方式"
-                              Type="Clickable" Logo="pack://application:,,,/images/Blocks/RedstoneBlock.png"
-                              EventType="打开网页" EventData="https://shimo.im/docs/KrkEVXz1K0frowAJ#anchor-7qQG" />
-                    <local:MyListItem Title="违法违规举报" Info="投诉、举报联机使用者的各类违法违规行为"
-                              Type="Clickable" EventType="打开网页" EventData="https://shimo.im/docs/rGrd8pY8xWkt6ryW#anchor-7x6P"
-                              Logo="pack://application:,,,/images/Blocks/RedstoneBlock.png" />
                 </StackPanel>
                 </StackPanel>
             </local:MyCard>
             </local:MyCard>
         </StackPanel>
         </StackPanel>

+ 1 - 2
Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml

@@ -83,7 +83,7 @@
                                 <local:ValidateRegex Regex="^[^\u4e00-\u9fa5]{6,}$" />
                                 <local:ValidateRegex Regex="^[^\u4e00-\u9fa5]{6,}$" />
                             </local:MyTextBox.ValidateRules>
                             </local:MyTextBox.ValidateRules>
                         </local:MyTextBox>
                         </local:MyTextBox>
-                        <local:MyButton Grid.Column="4" Grid.Row="4" Margin="0,0,15,0" Text="获取索引码" Padding="13,8" EventType="打开网页" EventData="https://shimo.im/docs/KrkEVXz1K0frowAJ" />
+                        <local:MyButton Grid.Column="4" Grid.Row="4" Margin="0,0,15,0" Text="获取索引码" Padding="13,8" local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://shimo.im/docs/KrkEVXz1K0frowAJ" />
                         <local:MyButton x:Name="BtnCertDone" Grid.Column="5" Grid.Row="4" Text="确定" ColorType="Highlight" Padding="13,8" IsEnabled="False" />
                         <local:MyButton x:Name="BtnCertDone" Grid.Column="5" Grid.Row="4" Text="确定" ColorType="Highlight" Padding="13,8" IsEnabled="False" />
                     </Grid>
                     </Grid>
                 </local:MyCard>
                 </local:MyCard>
@@ -229,7 +229,6 @@
             </Grid>
             </Grid>
         </Grid>
         </Grid>
         <Grid x:Name="PanAlways" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="15" Opacity="0.5">
         <Grid x:Name="PanAlways" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="15" Opacity="0.5">
-            <local:MyTextButton Text="联机服务由 速聚 授权提供" EventType="打开网页" EventData="https://www.the.bb/zh-CN.html" />
         </Grid>
         </Grid>
     </Grid>
     </Grid>
 </local:MyPageRight>
 </local:MyPageRight>

+ 1 - 1
Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml

@@ -44,7 +44,7 @@
             <local:MyLoading Text="正在加载联机模块" Margin="20,20,20,17" x:Name="Load" ShowProgress="True" HorizontalAlignment="Center" VerticalAlignment="Center" />
             <local:MyLoading Text="正在加载联机模块" Margin="20,20,20,17" x:Name="Load" ShowProgress="True" HorizontalAlignment="Center" VerticalAlignment="Center" />
         </local:MyCard>
         </local:MyCard>
         <Grid x:Name="PanAlways" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="15" Opacity="0.5">
         <Grid x:Name="PanAlways" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="15" Opacity="0.5">
-            <local:MyTextButton Text="联机服务由 速聚 授权提供" EventType="打开网页" EventData="https://www.the.bb/zh-CN.html" />
+            <local:MyTextButton Text="联机服务由 速聚 授权提供" local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.the.bb/zh-CN.html" />
         </Grid>
         </Grid>
     </Grid>
     </Grid>
 </local:MyPageRight>
 </local:MyPageRight>

+ 1 - 20
Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb

@@ -121,26 +121,7 @@ Public Class PageLinkIoi
     ''' 获取当前的玩家名。
     ''' 获取当前的玩家名。
     ''' </summary>
     ''' </summary>
     Public Shared Function GetPlayerName() As String
     Public Shared Function GetPlayerName() As String
-        '自动生成玩家名
-        If AutogenPlayerName Is Nothing Then
-            If IsPlayerNameValid(McLoginName) Then
-                AutogenPlayerName = McLoginName()
-            Else
-                AutogenPlayerName = "玩家 " & CType(GetHash(If(UniqueAddress, "")) Mod 1048576, Integer).ToString("x5").ToUpper
-            End If
-        End If
-        '获取玩家自定义的名称
-        Dim CustomName As String = Setup.Get("LinkName").ToString.Trim()
-        If CustomName <> "" Then
-            If IsPlayerNameValid(CustomName) Then
-                Return CustomName.Trim
-            Else
-                Hint("你所设置的玩家名存在异常,已被重置!", HintType.Critical)
-                Setup.Set("LinkName", "")
-            End If
-        End If
-        '使用自动生成的玩家名
-        Return AutogenPlayerName
+        Return ""
     End Function
     End Function
     Private Shared AutogenPlayerName As String = Nothing '并非由玩家自定义,而是自动生成的玩家名
     Private Shared AutogenPlayerName As String = Nothing '并非由玩家自定义,而是自动生成的玩家名
     ''' <summary>
     ''' <summary>

+ 231 - 300
Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml

@@ -29,8 +29,10 @@
                             <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
                             <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
                         </Image.Clip>
                         </Image.Clip>
                     </Image>
                     </Image>
-                    <local:MyButton Text="赞助 PCL !" Grid.Column="2" Grid.Row="0" Height="35" EventType="打开网页" EventData="https://afdian.com/a/LTCat" ColorType="Highlight" />
-                    <local:MyButton Text="查看源代码" Grid.Column="2" Grid.Row="1" Height="35" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2" />
+                    <local:MyButton Text="赞助 PCL !" Grid.Column="2" Grid.Row="0" Height="35" ColorType="Highlight"
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://afdian.com/a/LTCat" />
+                    <local:MyButton Text="查看源代码" Grid.Column="2" Grid.Row="1" Height="35" 
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/Hex-Dragon/PCL2" />
                 </Grid>
                 </Grid>
             </local:MyCard>
             </local:MyCard>
             <local:MyCard Margin="0,0,0,15" Title="特别鸣谢">
             <local:MyCard Margin="0,0,0,15" Title="特别鸣谢">
@@ -51,11 +53,11 @@
                         <ColumnDefinition Width="150" />
                         <ColumnDefinition Width="150" />
                     </Grid.ColumnDefinitions>
                     </Grid.ColumnDefinitions>
                     <local:MyListItem Title="bangbang93" Margin="-5,0,15,0" IsHitTestVisible="False" Info="提供 BMCLAPI 镜像源和 Forge 安装工具,详见 https://bmclapi.bangbang93.com" Grid.Row="0" Grid.Column="1" />
                     <local:MyListItem Title="bangbang93" Margin="-5,0,15,0" IsHitTestVisible="False" Info="提供 BMCLAPI 镜像源和 Forge 安装工具,详见 https://bmclapi.bangbang93.com" Grid.Row="0" Grid.Column="1" />
-                    <local:MyButton Text="赞助镜像源" Grid.Row="0" Grid.Column="2" Height="35" EventType="打开网页" EventData="https://afdian.com/a/bangbang93" />
+                    <local:MyButton Text="赞助镜像源" Grid.Row="0" Grid.Column="2" Height="35" local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://afdian.com/a/bangbang93" />
                     <local:MyListItem Title="MC 百科" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 Mod 名称的中文翻译和更多相关信息!" Grid.Row="1" Grid.Column="1" />
                     <local:MyListItem Title="MC 百科" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 Mod 名称的中文翻译和更多相关信息!" Grid.Row="1" Grid.Column="1" />
-                    <local:MyButton Text="打开百科" Grid.Row="1" Grid.Column="2" Height="35" EventType="打开网页" EventData="https://www.mcmod.cn" />
+                    <local:MyButton Text="打开百科" Grid.Row="1" Grid.Column="2" Height="35" local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.mcmod.cn" />
                     <local:MyListItem Title="z0z0r4" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 MCIM 社区资源镜像源和帮助库图床!" Grid.Row="2" Grid.Column="1" />
                     <local:MyListItem Title="z0z0r4" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 MCIM 社区资源镜像源和帮助库图床!" Grid.Row="2" Grid.Column="1" />
-                    <local:MyButton Text="MCIM 主页" Grid.Row="2" Grid.Column="2" Height="35" EventType="打开网页" EventData="https://github.com/mcmod-info-mirror" />
+                    <local:MyButton Text="MCIM 主页" Grid.Row="2" Grid.Column="2" Height="35" local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/mcmod-info-mirror" />
                     <local:MyListItem Title="00ll00" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 Java Launch Wrapper 和一些重要服务支持!" Grid.Row="3" Grid.Column="1" />
                     <local:MyListItem Title="00ll00" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 Java Launch Wrapper 和一些重要服务支持!" Grid.Row="3" Grid.Column="1" />
                     <local:MyListItem Title="Patrick" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="设计并制作了 PCL 图标,让我从做图标的水深火热中得到了解脱……" Grid.Row="4" Grid.Column="1" />
                     <local:MyListItem Title="Patrick" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="设计并制作了 PCL 图标,让我从做图标的水深火热中得到了解脱……" Grid.Row="4" Grid.Column="1" />
                     <local:MyListItem Title="Hao_Tian" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="在内测中找出了一大堆没人想得到的诡异 Bug,有非同寻常的 Bug 体质" Grid.Row="5" Grid.Column="1" />
                     <local:MyListItem Title="Hao_Tian" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="在内测中找出了一大堆没人想得到的诡异 Bug,有非同寻常的 Bug 体质" Grid.Row="5" Grid.Column="1" />
@@ -105,288 +107,217 @@
             </local:MyCard>
             </local:MyCard>
             <local:MyCard Margin="0,0,0,15" Title="赞助者">
             <local:MyCard Margin="0,0,0,15" Title="赞助者">
                 <StackPanel Margin="25,40,10,20">
                 <StackPanel Margin="25,40,10,20">
-                    <TextBlock HorizontalAlignment="Left" Text="以下是 2025 年 8 月的高级赞助者们!感谢各位对 PCL 和伟大的咕咕咕事业的支持!" Margin="0,0,25,5.5" TextTrimming="None" TextWrapping="WrapWithOverflow" />
-                    <WrapPanel Margin="0,0,0,10">
                        <TextBlock Width="120" Margin="0,1.5" Text="粒子鹰" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="暮色黄昏" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="未殃" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="咸鱼" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Claud_9527" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Qqsnd" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="☆Kiana☆" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="晓枫-橘味C" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Ferderica" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="过路人" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="寒溪" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小咪vv" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="容小狸" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="南宫流溪" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="懒包子" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="王家有政" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="??????" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="念念" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="启233" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="爱小黎" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="北笙T-T" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="listpmater" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="朝朝超级の好运" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="低调" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="老公" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="雍仕" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="AAboss" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="闲来无事" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="omgddcsl" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="轻舟" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="浮生Yui" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="YangFK" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="一曲勺" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="时白Herhilo" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="寒冬落雨" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="柯基0v0" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="月兔" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="qkowo" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="沐白" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="每雨天" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="AiNo" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="木叶飞舞之处" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="wilbert_laogao" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="悠芸" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="黑白盗贼" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="PotCatawa" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小王" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="花还没开" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="PixelFrontier像素边界" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="坊之岬" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="HuTu" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="爱言叶ray" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="lenrin" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="." />
-                        <TextBlock Width="120" Margin="0,1.5" Text="这是一头灯笼花" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="肖然星辰" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="QMX" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="山外诗" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="刘" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="萧风溪水" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="风落" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="星星绒绒" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Ciallo~(∠?ω" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="九十九" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Guuzen" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="极光" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="9331" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="钱程" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="杏坂和人" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="jinlijsbjaj" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="海豹" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="洛玉衡kale" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="柠乔" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="N0NiL" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="阿卡零" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="ADAA" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="岚瑬" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="爱料理的猫" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="01B8" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="MCDJ喜羊羊" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="hwch55" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="棉花糖十五" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="鱼头" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="拉普兰德天下第一" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="KGIKUY" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="初洛" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="z .e.r.o" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="独孤真菜" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="阿囊" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="rrrwcsza" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="守卫军的杂鱼兵" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="念安" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="叶" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="rreorr" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="星辰大海" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="随便" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="烤着吃的鱼" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="大概是" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="闹山麻雀" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="我是(⊙o⊙)啥?笔" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="北纬44" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="呼呼呼" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="嘎嘎不咕咕" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="kronos" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="有个好梦,同志" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Amojun" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="bat-744" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="那谁" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="AYOSO" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="抽抽" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="柚子皮皮皮" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="白玉京" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="ALPSL" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="我爱喝兽奶" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="wwq557" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="爱莉希雅" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="上善若水" />
+                    <TextBlock HorizontalAlignment="Left" Text="以下是 2025 年 9 月的高级赞助者们!感谢各位对 PCL 和伟大的咕咕咕事业的支持!" Margin="0,0,25,5.5" TextTrimming="None" TextWrapping="WrapWithOverflow" />
+                    <WrapPanel Margin="0,0,0,10">
                        <TextBlock Width="120" Margin="0,1.5" Text="lecoe" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="QIUQI" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Aquadtr" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="stb" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="mco" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="楠" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="砂糖蜂蜜锡红茶" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="小宸老弟" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Le Play" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="烨神" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="Kamazi_Yuka5201" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="滚动的拉布拉多" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="王牟" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Lazarus" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="幻觉" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="凌天" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="lIIIllIIllIIl" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Zsol" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="榴莲" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Night Voyager" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="JOHNJIANG" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="柒月" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="不温柔先森" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Freewind" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="纯萧子" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="扇" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Amturester" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="maigm" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="JO23" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="JCNB" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="星尘" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="姜桐桐233" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="AkaneRyoba" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="南山必胜客" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="57" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="咸菜萝卜煲" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="shawnlong" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="DDNGSZX1" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="KaguraZaka" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="谶纛" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="666" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="betwangzai" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="卑微小林" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="fjvg" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="TH-x" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Q" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="清鸢" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="P·咕·D·咕咕·F" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="顾木" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="LAZY" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="沙发上的沙发" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="有熊子来" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="月下誓约,予爱以心" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="超级白给马" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="fwlqpalzmd" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="安执妄dying" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="黄开心" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="于是" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="荆槐序" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="三千游魂" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="^ ^" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="zz" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="爱炫鱼的猫" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="玄月" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="BRfake" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Lucien_Liu" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="煎bingo子" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="对方正在输入..." />
+                        <TextBlock Width="120" Margin="0,1.5" Text="hakureireimu" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="wwxa2019" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="TAIGA" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="墨玉" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Jane" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="雪下丨残影" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="未命名3742" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="烧冻鸡翅" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="嘟嘟可" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="利姆露" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ATH9" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="教主" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="落雪有声" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="苏子霄" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="一握砂" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="马春明" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="远力高" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="玉米片" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="sun次方" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="憨憨橙子" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="染年" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="准江月" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="蓝叶" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ELUCIDATE" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="兔饼干哈哈" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Styx誓约" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="V" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="月芸柔" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="灯火鬼魂" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="A" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="项刀关志" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="剑鱼776" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="叶明心" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Ashlxy" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="小时" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="fisher426" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="老罗" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="百忧解" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="杨三海" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="锂化氩" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="饺子川" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="流年遥想" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="基围虾" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="山有木兮325" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="37号村民" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="白承宣" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="某蓝毛借钱不还的贝斯手" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="白狼" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="工蜂" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="封不觉" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="南樵鱼子" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="寂-御神幻狐" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="好" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="NeonSoda" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="UNICORN" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="红石beaker" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="EliasLSK" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="思考人生" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="L" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="起床了" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="服群" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="云符爱睡觉" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="幽幽秋" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="FAMS" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="会变身的栖仔" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="baka王" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="深海带鱼" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="四戒梨子" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="无限" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Xc_AcE" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="@6#" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="molFish" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="雪见" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="AAALLLAAANNN" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="华氏451" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="纸不好" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="博衍" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="志摩凛" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="八云蓝LIN" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="LittleBlue" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="老梦" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="动漫男主" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Red_橙子" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Junmucheng" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="无印" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="窗前露" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="1111111" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="什雨" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="七日 SEVENDAYS" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Gǔdù" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="白吃白喝" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="路明非" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="子不语" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="王璟" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="阿星" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="小狗很担心你有没有好好喝水" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="hjhmeibao" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="网络白字话事人" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="想要点好运" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Timmy1950" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="栩" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="玛卡巴卡" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="楞可" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="顶级牛板筋收藏家" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="HansOffice" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="466某某某" />
                         <TextBlock Width="120" Margin="0,1.5" Text="zztzzy" />
                         <TextBlock Width="120" Margin="0,1.5" Text="zztzzy" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="SHiao_Yui" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="一只花生" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="黑凤梨熊?" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="sdad" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="BLONG" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="787878" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="myry668" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="f三文鱼f" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="apqow" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="懒梦棉花糖" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="HaiMing" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Evernight" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="CieloNeet" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="不青盆" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Purelimiter" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="丨白丨" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="喵" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="雨过山秋" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="急了" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="歌斐木" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="公子天邪" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="爱发癫" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="洪某某" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="漫卡洛樱" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="云与海" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="子曰" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="CyrilZ" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="笑颜如花兮" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="咸鱼超人FF酱!" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="达达利鸭" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="TFofN" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Kumo" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="江俞白" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="聆风忘年" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="五符临门" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="亭书浅墨" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="翀归滉" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Anmuer" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="青天揽明月" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="鲫鱼崽" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="PPTNPC" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="vIccvI" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="GOOD" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="归零之诗" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="ppp" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Ethanhmm1234" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="库里斯库拉" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="被伪安全软件坑害的普通人" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="无元亓" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Raven" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="看看你的jjp" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="日月皆如水" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="gzwmmm" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="早上鸥" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="CJT" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="幽永森" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="东海帝皇" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="三岁" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="shui12dkhigl" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="豪猪" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="再相遇" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="?白樱Hakura" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="IssacPyromaniac" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="纯真丁一郎" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="岩之龙" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="星之金白" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="laocao" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="DLC...是什么" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="我家老米" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="TINY" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="只会coding的农民工" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="明月一行" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="nanako" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="辛霏Aiwx" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="奇妙的白给之旅" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="你咋不上天呢^_^" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="冰糕" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="sadas" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小叮当搞快点" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="leu" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="休伯利安本来的舰长" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="天诺一字号仓鼠" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="真爱粉只因龙" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="绝望的温度" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="busituteng" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="SiC" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="契特卡拉概念机" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="我还能再在死线挣扎一会" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="02_G1nTonic" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="渊" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="1" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小楠" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="阿冰" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="EliasG" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="jhx" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="zyccmyk" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="luxin" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="我是我" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="13462738215" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="minecraft java" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="那是小林吖" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="GITATIG" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Musketeer" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="AuraSpeedJerry" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="luckymore" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="hcrmhj" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="我好焦灼啊" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小王同学" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="MoDust2131" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="啊超" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Ander·Les" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Niko" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="五个麦卷" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="b0Ard" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="龙猫的专属粉丝" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="雾念~" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="烬灵梦" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="蝶落染花香" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="照井黎Terui_Rei" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="???狼??王?" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="辉光离子管" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="联开开" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="XIAOSHUAI" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="KMYLS" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="聽音" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="oPzPo" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="dream" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="123" />
                         <TextBlock Width="120" Margin="0,1.5" Text="Zik yeung" />
                         <TextBlock Width="120" Margin="0,1.5" Text="Zik yeung" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Coco" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Ricardo" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="2969307002" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="鬼卿" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="wihz" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="九平" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="liu" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="PickStars" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="我真的事仁济啊(恼)" />
                         <TextBlock Width="120" Margin="0,1.5" Text="李白" />
                         <TextBlock Width="120" Margin="0,1.5" Text="李白" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="霜木翔凤" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="岽龙" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="joksqp" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="what are you 说啥嘞?" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="木得爱_没人疼" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小黑子?" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="删除线Strikethrough" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="REMOTE" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="H" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Parapha" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="南吕十三" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="米帝" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="zdxff" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="蔚空" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="cleverboy1066" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="烤老鼠" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="TS彦祖" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="狮某人" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="lmst_myh" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="耐·若何" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="qvq" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="刀客塔" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="mika" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="金尘" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="喜之狼与灰太羊" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="天才小宋" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="MdsR" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="诗和雨夜" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Samueldong" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="BBK" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="雪枫Sople" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="一坨灰" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="RI_CK" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="ximeng22" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="天天122_" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="李小浩&amp;mod" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Southsheng" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="HurgerLiPy" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Crishen70400" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="孤独" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="夏末彡微凉" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="山本樱桃不是df" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="陶牧馆长" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="一二三四" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="17382073935" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="不知道" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="重庆你周哥" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="熳ai" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Rainbow Stars" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="无名岩" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="快乐小子" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="沙西凯" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="一只小孽QAQ" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="五度左右" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="咖喱鱼" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="千叶GG" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="泽木_Zemu" />
                     </WrapPanel>
                     </WrapPanel>
                     <Grid Height="35">
                     <Grid Height="35">
                         <Grid.ColumnDefinitions>
                         <Grid.ColumnDefinitions>
@@ -395,7 +326,7 @@
                             <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
                             <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
                         </Grid.ColumnDefinitions>
                         </Grid.ColumnDefinitions>
                         <local:MyButton x:Name="BtnDonateOutput" Grid.Column="0" MinWidth="140" Text="生成解锁码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" Visibility="Collapsed" />
                         <local:MyButton x:Name="BtnDonateOutput" Grid.Column="0" MinWidth="140" Text="生成解锁码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" Visibility="Collapsed" />
-                        <local:MyButton x:Name="BtnDonateDonate" Grid.Column="0" MinWidth="140" Text="赞助作者" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" EventType="打开网页" EventData="https://afdian.com/a/LTCat" />
+                        <local:MyButton x:Name="BtnDonateDonate" Grid.Column="0" MinWidth="140" Text="赞助作者" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://afdian.com/a/LTCat" />
                         <local:MyButton x:Name="BtnDonateCopy" Grid.Column="1" MinWidth="140" Text="复制识别码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
                         <local:MyButton x:Name="BtnDonateCopy" Grid.Column="1" MinWidth="140" Text="复制识别码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
                         <local:MyButton x:Name="BtnDonateInput" Grid.Column="2" MinWidth="140" Text="输入解锁码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
                         <local:MyButton x:Name="BtnDonateInput" Grid.Column="2" MinWidth="140" Text="输入解锁码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
                     </Grid>
                     </Grid>
@@ -410,8 +341,8 @@
                     <TextBlock Text="其他信息" TextWrapping="Wrap" Margin="0,10,0,4" FontWeight="Bold" />
                     <TextBlock Text="其他信息" TextWrapping="Wrap" Margin="0,10,0,4" FontWeight="Bold" />
                     <TextBlock Margin="0,0,0,2" LineHeight="17" Text="Copyright © 龙腾猫跃 2016. All Rights Reserved.&#13;计算机软件著作权登记号:2020SR0875133&#13;违法违规行为举报投诉邮箱:[email protected]&#13;非 MINECRAFT 官方产品。未经 MOJANG 或 MICROSOFT 批准,也不与 MOJANG 或 MICROSOFT 关联。" TextWrapping="Wrap" />
                     <TextBlock Margin="0,0,0,2" LineHeight="17" Text="Copyright © 龙腾猫跃 2016. All Rights Reserved.&#13;计算机软件著作权登记号:2020SR0875133&#13;违法违规行为举报投诉邮箱:[email protected]&#13;非 MINECRAFT 官方产品。未经 MOJANG 或 MICROSOFT 批准,也不与 MOJANG 或 MICROSOFT 关联。" TextWrapping="Wrap" />
                     <StackPanel Orientation="Horizontal" Height="35" HorizontalAlignment="Left" Margin="0,12,0,0">
                     <StackPanel Orientation="Horizontal" Height="35" HorizontalAlignment="Left" Margin="0,12,0,0">
-                        <local:MyButton Text="用户协议与免责声明" Width="170" Padding="13,0" Margin="0,0,20,0" ColorType="Highlight" EventType="打开网页" EventData="https://shimo.im/docs/rGrd8pY8xWkt6ryW" />
-                        <local:MyButton Text="开源代码" Width="140" Padding="13,0" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2" />
+                        <local:MyButton Text="用户协议与免责声明" Width="170" Padding="13,0" Margin="0,0,20,0" ColorType="Highlight" local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://shimo.im/docs/rGrd8pY8xWkt6ryW" />
+                        <local:MyButton Text="开源代码" Width="140" Padding="13,0" local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/Hex-Dragon/PCL2" />
                     </StackPanel>
                     </StackPanel>
                 </StackPanel>
                 </StackPanel>
             </local:MyCard>
             </local:MyCard>
@@ -443,49 +374,49 @@
                     <TextBlock Grid.Row="0" Grid.Column="0" Text="Java Launch Wrapper" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="0" Grid.Column="0" Text="Java Launch Wrapper" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2022 00ll00&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
                     <TextBlock Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2022 00ll00&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
                     <local:MyButton Grid.Row="1" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
                     <local:MyButton Grid.Row="1" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/00ll00/java_launch_wrapper" />
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/00ll00/java_launch_wrapper" />
                     <local:MyButton Grid.Row="1" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
                     <local:MyButton Grid.Row="1" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/00ll00/java_launch_wrapper?tab=MIT-1-ov-file#readme" />
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/00ll00/java_launch_wrapper?tab=MIT-1-ov-file#readme" />
 
 
                     <TextBlock Grid.Row="2" Grid.Column="0" Text="Newtonsoft.Json" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="2" Grid.Column="0" Text="Newtonsoft.Json" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2007 James Newton-King&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
                     <TextBlock Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2007 James Newton-King&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
                     <local:MyButton Grid.Row="3" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
                     <local:MyButton Grid.Row="3" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://www.newtonsoft.com/json" />
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.newtonsoft.com/json" />
                     <local:MyButton Grid.Row="3" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
                     <local:MyButton Grid.Row="3" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://opensource.org/licenses/MIT" />
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://opensource.org/licenses/MIT" />
                     
                     
                     <TextBlock Grid.Row="4" Grid.Column="0" Text="NAudio" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="4" Grid.Column="0" Text="NAudio" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2015 Mark Heath ([email protected])&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
                     <TextBlock Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2015 Mark Heath ([email protected])&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
                     <local:MyButton Grid.Row="5" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
                     <local:MyButton Grid.Row="5" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/naudio/NAudio" />
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/naudio/NAudio" />
                     <local:MyButton Grid.Row="5" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left" 
                     <local:MyButton Grid.Row="5" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left" 
-                                    EventType="打开网页" EventData="https://github.com/naudio/NAudio/blob/master/license.txt" />
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/naudio/NAudio/blob/master/license.txt" />
                     
                     
                     <TextBlock Grid.Row="6" Grid.Column="0" Text="Ookii.Dialogs" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="6" Grid.Column="0" Text="Ookii.Dialogs" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2009 Sven Groot (Ookii.org)" TextWrapping="Wrap" />
                     <TextBlock Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2009 Sven Groot (Ookii.org)" TextWrapping="Wrap" />
                     <local:MyButton Grid.Row="7" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
                     <local:MyButton Grid.Row="7" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://www.ookii.org/Software/Dialogs/" />
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.ookii.org/Software/Dialogs/" />
                     <local:MyButton Grid.Row="7" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
                     <local:MyButton Grid.Row="7" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/caioproiete/ookii-dialogs-wpf/blob/master/LICENSE" />
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/caioproiete/ookii-dialogs-wpf/blob/master/LICENSE" />
                     
                     
                     <TextBlock Grid.Row="8" Grid.Column="0" Text="Iconmonstr" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="8" Grid.Column="0" Text="Iconmonstr" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2019 iconmonstr (Alexander Kahlkopf)" TextWrapping="Wrap" />
                     <TextBlock Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2019 iconmonstr (Alexander Kahlkopf)" TextWrapping="Wrap" />
                     <local:MyButton Grid.Row="9" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
                     <local:MyButton Grid.Row="9" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://iconmonstr.com/" />
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://iconmonstr.com/" />
                     <local:MyButton Grid.Row="9" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
                     <local:MyButton Grid.Row="9" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://iconmonstr.com/license/" />
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://iconmonstr.com/license/" />
                     
                     
                     <TextBlock Grid.Row="10" Grid.Column="0" Text="libwebp wrapper for .NET" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="10" Grid.Column="0" Text="libwebp wrapper for .NET" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2012 Imazen&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
                     <TextBlock Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2012 Imazen&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
                     <local:MyButton Grid.Row="11" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
                     <local:MyButton Grid.Row="11" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/imazen/libwebp-net" />
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/imazen/libwebp-net" />
                     <local:MyButton Grid.Row="11" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
                     <local:MyButton Grid.Row="11" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/imazen/libwebp-net/blob/master/LICENSE.txt" />
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/imazen/libwebp-net/blob/master/LICENSE.txt" />
 
 
                     <TextBlock Grid.Row="12" Grid.Column="0" Text="Iconfont" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="12" Grid.Column="0" Text="Iconfont" TextWrapping="Wrap" FontWeight="Bold" />
                     <TextBlock Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © ALIMAMA MUX, powered by alimama THX." TextWrapping="Wrap" />
                     <TextBlock Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © ALIMAMA MUX, powered by alimama THX." TextWrapping="Wrap" />
                     <local:MyButton Grid.Row="13" Grid.Column="2" MinWidth="140" Text="查看原网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
                     <local:MyButton Grid.Row="13" Grid.Column="2" MinWidth="140" Text="查看原网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://www.iconfont.cn/" />
+                                    local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.iconfont.cn/" />
 
 
                 </Grid>
                 </Grid>
             </local:MyCard>
             </local:MyCard>

+ 2 - 2
Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb

@@ -18,8 +18,8 @@
 
 
     End Sub
     End Sub
 
 
-    Public Shared Sub CopyUniqueAddress() Handles BtnDonateCopy.Click
-        ClipboardSet(UniqueAddress)
+    Public Shared Sub CopyIdentify() Handles BtnDonateCopy.Click
+        ClipboardSet(Identify)
     End Sub
     End Sub
     Private Sub BtnDonateCodeInput_Click() Handles BtnDonateInput.Click
     Private Sub BtnDonateCodeInput_Click() Handles BtnDonateInput.Click
         DonateCodeInput()
         DonateCodeInput()

+ 1 - 1
Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb

@@ -71,7 +71,7 @@
     Public Shared Sub OnItemClick(Entry As HelpEntry)
     Public Shared Sub OnItemClick(Entry As HelpEntry)
         Try
         Try
             If Entry.IsEvent Then
             If Entry.IsEvent Then
-                ModEvent.ProcessCustomEvents(Entry.EventType, Entry.EventData, Nothing)
+                CustomEvent.Raise(Entry.EventType, Entry.EventData)
             Else
             Else
                 EnterHelpPage(Entry)
                 EnterHelpPage(Entry)
             End If
             End If

+ 4 - 2
Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb

@@ -18,8 +18,10 @@
         If Content = "" Then Throw New Exception("帮助 xaml 文件为空")
         If Content = "" Then Throw New Exception("帮助 xaml 文件为空")
         Try
         Try
             '修改时应同时修改 PageLaunchRight.LoadContent
             '修改时应同时修改 PageLaunchRight.LoadContent
-            Content = HelpArgumentReplace(Content)
-            If Content.Contains("xmlns") Then Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间
+            Content = ArgumentReplace(Content, AddressOf EscapeXML)
+            Do While Content.Contains("xmlns")
+                Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间
+            Loop
             Content = "<StackPanel xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:sys=""clr-namespace:System;assembly=mscorlib"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:local=""clr-namespace:PCL;assembly=Plain Craft Launcher 2"">" & Content & "</StackPanel>"
             Content = "<StackPanel xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:sys=""clr-namespace:System;assembly=mscorlib"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:local=""clr-namespace:PCL;assembly=Plain Craft Launcher 2"">" & Content & "</StackPanel>"
             Me.Entry = Entry
             Me.Entry = Entry
             PanCustom.Children.Clear()
             PanCustom.Children.Clear()

+ 13 - 13
Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb

@@ -150,14 +150,14 @@
         Dim NewFolder As String = ""
         Dim NewFolder As String = ""
         '检查是否有下载任务
         '检查是否有下载任务
         If HasDownloadingTask() Then
         If HasDownloadingTask() Then
-            Hint("在下载任务进行时,无法添加游戏文件夹!", HintType.Critical)
+            Hint("在下载任务进行时,无法添加游戏文件夹!", HintType.Red)
             Return
             Return
         End If
         End If
         Try
         Try
             '获取输入
             '获取输入
             NewFolder = SelectFolder()
             NewFolder = SelectFolder()
             If NewFolder = "" Then Return
             If NewFolder = "" Then Return
-            If NewFolder.Contains("!") OrElse NewFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Critical) : Return
+            If NewFolder.Contains("!") OrElse NewFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Red) : Return
             '要求输入显示名称
             '要求输入显示名称
             Dim SplitedNames As String() = NewFolder.TrimEnd("\").Split("\")
             Dim SplitedNames As String() = NewFolder.TrimEnd("\").Split("\")
             Dim DefaultName As String = If(SplitedNames.Last = ".minecraft", If(SplitedNames.Count >= 3, SplitedNames(SplitedNames.Count - 2), ""), SplitedNames.Last)
             Dim DefaultName As String = If(SplitedNames.Last = ".minecraft", If(SplitedNames.Count >= 3, SplitedNames(SplitedNames.Count - 2), ""), SplitedNames.Last)
@@ -182,7 +182,7 @@
                 '检查文件夹权限
                 '检查文件夹权限
                 If Not CheckPermission(FolderPath) Then
                 If Not CheckPermission(FolderPath) Then
                     If ShowHint Then
                     If ShowHint Then
-                        Hint("添加文件夹失败:PCL 没有访问该文件夹的权限!", HintType.Critical)
+                        Hint("添加文件夹失败:PCL 没有访问该文件夹的权限!", HintType.Red)
                         Return
                         Return
                     Else
                     Else
                         Throw New Exception("PCL 没有访问文件夹的权限:" & FolderPath)
                         Throw New Exception("PCL 没有访问文件夹的权限:" & FolderPath)
@@ -207,12 +207,12 @@
                     If Folder.Split(">")(1) = FolderPath Then
                     If Folder.Split(">")(1) = FolderPath Then
                         IsAdded = True
                         IsAdded = True
                         If Folder.Split(">")(0) = DisplayName Then
                         If Folder.Split(">")(0) = DisplayName Then
-                            If ShowHint Then Hint("此文件夹已在列表中!", HintType.Info)
+                            If ShowHint Then Hint("此文件夹已在列表中!", HintType.Blue)
                             Return
                             Return
                         Else
                         Else
                             Folders(i) = DisplayName & ">" & FolderPath
                             Folders(i) = DisplayName & ">" & FolderPath
                             IsReplace = True
                             IsReplace = True
-                            If ShowHint Then Hint("文件夹名称已更新为 " & DisplayName & " !", HintType.Finish)
+                            If ShowHint Then Hint("文件夹名称已更新为 " & DisplayName & " !", HintType.Green)
                         End If
                         End If
                         Exit For
                         Exit For
                     End If
                     End If
@@ -226,7 +226,7 @@
                 McFolderListLoader.Start(IsForceRestart:=True)
                 McFolderListLoader.Start(IsForceRestart:=True)
                 '提示
                 '提示
                 If IsReplace Then Return
                 If IsReplace Then Return
-                If ShowHint Then Hint("文件夹 " & DisplayName & " 已添加!", HintType.Finish)
+                If ShowHint Then Hint("文件夹 " & DisplayName & " 已添加!", HintType.Green)
                 '检查是否为根目录整合包,自动关闭版本隔离
                 '检查是否为根目录整合包,自动关闭版本隔离
                 '1. 根目录中存在数个 Mod
                 '1. 根目录中存在数个 Mod
                 Dim ModFolder As New DirectoryInfo(FolderPath & "mods\")
                 Dim ModFolder As New DirectoryInfo(FolderPath & "mods\")
@@ -257,7 +257,7 @@
     Public Sub Create_Click()
     Public Sub Create_Click()
         '检查是否有下载任务
         '检查是否有下载任务
         If HasDownloadingTask() Then
         If HasDownloadingTask() Then
-            Hint("在下载任务进行时,无法创建游戏文件夹!", HintType.Critical)
+            Hint("在下载任务进行时,无法创建游戏文件夹!", HintType.Red)
             Return
             Return
         End If
         End If
         If Not Directory.Exists(Path & ".minecraft\") Then
         If Not Directory.Exists(Path & ".minecraft\") Then
@@ -265,7 +265,7 @@
             Directory.CreateDirectory(Path & ".minecraft\versions\")
             Directory.CreateDirectory(Path & ".minecraft\versions\")
             Setup.Set("LaunchFolderSelect", "$.minecraft\")
             Setup.Set("LaunchFolderSelect", "$.minecraft\")
             McFolderLauncherProfilesJsonCreate(Path & ".minecraft\")
             McFolderLauncherProfilesJsonCreate(Path & ".minecraft\")
-            Hint("新建 .minecraft 文件夹成功!", HintType.Finish)
+            Hint("新建 .minecraft 文件夹成功!", HintType.Green)
         End If
         End If
         McFolderListLoader.Start(IsForceRestart:=True)
         McFolderListLoader.Start(IsForceRestart:=True)
     End Sub
     End Sub
@@ -307,7 +307,7 @@
             Next
             Next
             '保存
             '保存
             Setup.Set("LaunchFolders", If(Not Folders.Any(), "", Join(Folders.ToArray, "|")))
             Setup.Set("LaunchFolders", If(Not Folders.Any(), "", Join(Folders.ToArray, "|")))
-            Hint(If(Folder.Type = McFolderType.Custom, "文件夹 " & Name & " 已从列表中移除!", "文件夹名称已复原!"), HintType.Finish)
+            Hint(If(Folder.Type = McFolderType.Custom, "文件夹 " & Name & " 已从列表中移除!", "文件夹名称已复原!"), HintType.Green)
             McFolderListLoader.Start(IsForceRestart:=True)
             McFolderListLoader.Start(IsForceRestart:=True)
 
 
         Catch ex As Exception
         Catch ex As Exception
@@ -336,10 +336,10 @@
         Sub()
         Sub()
             '删除文件夹
             '删除文件夹
             Try
             Try
-                Hint("正在" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Info)
+                Hint("正在" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Blue)
                 DeleteDirectory(Folder.Path)
                 DeleteDirectory(Folder.Path)
                 If DeleteText = "清空" Then Directory.CreateDirectory(Folder.Path)
                 If DeleteText = "清空" Then Directory.CreateDirectory(Folder.Path)
-                Hint("已" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Finish)
+                Hint("已" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Green)
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, DeleteText & "文件夹 " & Folder.Name & " 失败", LogLevel.Hint)
                 Log(ex, DeleteText & "文件夹 " & Folder.Name & " 失败", LogLevel.Hint)
             Finally
             Finally
@@ -389,7 +389,7 @@
             Next
             Next
             '如果没有添加过,则添加进去(因为修改了默认项的名称)
             '如果没有添加过,则添加进去(因为修改了默认项的名称)
             If Not IsAdded Then Folders.Add(NewName & ">" & Folder.Path)
             If Not IsAdded Then Folders.Add(NewName & ">" & Folder.Path)
-            Hint("文件夹名称已更新为 " & NewName & " !", HintType.Finish)
+            Hint("文件夹名称已更新为 " & NewName & " !", HintType.Green)
             '保存
             '保存
             Setup.Set("LaunchFolders", Join(Folders.ToArray, "|"))
             Setup.Set("LaunchFolders", Join(Folders.ToArray, "|"))
             McFolderListLoader.Start(IsForceRestart:=True)
             McFolderListLoader.Start(IsForceRestart:=True)
@@ -403,7 +403,7 @@
         If Not e.RaiseByMouse OrElse Not sender.Checked Then Return
         If Not e.RaiseByMouse OrElse Not sender.Checked Then Return
         '检查是否有下载任务
         '检查是否有下载任务
         If HasDownloadingTask(True) Then
         If HasDownloadingTask(True) Then
-            Hint("在下载任务进行时,无法切换游戏文件夹!", HintType.Critical)
+            Hint("在下载任务进行时,无法切换游戏文件夹!", HintType.Red)
             e.Handled = True
             e.Handled = True
             Return
             Return
         End If
         End If

+ 3 - 18
Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb

@@ -114,22 +114,7 @@
             Log(ex, "将版本列表转换显示时失败", LogLevel.Feedback)
             Log(ex, "将版本列表转换显示时失败", LogLevel.Feedback)
         End Try
         End Try
     End Sub
     End Sub
-    Public Shared Function McVersionListItem(Version As McVersion) As MyListItem
-        Dim NewItem As New MyListItem With {.Title = Version.Name, .Info = Version.Info, .Height = 42, .Tag = Version, .SnapsToDevicePixels = True, .Type = MyListItem.CheckType.Clickable}
-        Try
-            If Version.Logo.EndsWith("PCL\Logo.png") Then
-                NewItem.Logo = Version.Path & "PCL\Logo.png" '修复老版本中,存储的自定义 Logo 使用完整路径,导致移动后无法加载的 Bug
-            Else
-                NewItem.Logo = Version.Logo
-            End If
-        Catch ex As Exception
-            Log(ex, "加载版本图标失败", LogLevel.Hint)
-            NewItem.Logo = "pack://application:,,,/images/Blocks/RedstoneBlock.png"
-        End Try
-        NewItem.ContentHandler = AddressOf McVersionListContent
-        Return NewItem
-    End Function
-    Private Shared Sub McVersionListContent(sender As MyListItem, e As EventArgs)
+    Public Shared Sub McVersionListContent(sender As MyListItem, e As EventArgs)
         Dim Version As McVersion = sender.Tag
         Dim Version As McVersion = sender.Tag
         '注册点击事件
         '注册点击事件
         AddHandler sender.Click, AddressOf Item_Click
         AddHandler sender.Click, AddressOf Item_Click
@@ -225,10 +210,10 @@
                     IniClearCache(Version.Path & "PCL\Setup.ini")
                     IniClearCache(Version.Path & "PCL\Setup.ini")
                     If IsShiftPressed Then
                     If IsShiftPressed Then
                         DeleteDirectory(Version.Path)
                         DeleteDirectory(Version.Path)
-                        Hint("版本 " & Version.Name & " 已永久删除!", HintType.Finish)
+                        Hint("版本 " & Version.Name & " 已永久删除!", HintType.Green)
                     Else
                     Else
                         FileIO.FileSystem.DeleteDirectory(Version.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
                         FileIO.FileSystem.DeleteDirectory(Version.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
-                        Hint("版本 " & Version.Name & " 已删除到回收站!", HintType.Finish)
+                        Hint("版本 " & Version.Name & " 已删除!", HintType.Green)
                     End If
                     End If
                 Case 2
                 Case 2
                     Return
                     Return

+ 23 - 8
Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb

@@ -227,7 +227,7 @@
                 Me.Value = Value
                 Me.Value = Value
                 Me.Source = Source
                 Me.Source = Source
                 Me.Type = If(Value, New Object).GetType
                 Me.Type = If(Value, New Object).GetType
-                Me.DefaultValueEncoded = If(Encoded, SecretEncrypt(Value, "PCL" & UniqueAddress), Value)
+                Me.DefaultValueEncoded = If(Encoded, SecretEncrypt(Value, "PCL" & Identify), Value)
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "初始化 SetupEntry 失败", LogLevel.Feedback) '#5095 的 fallback
                 Log(ex, "初始化 SetupEntry 失败", LogLevel.Feedback) '#5095 的 fallback
             End Try
             End Try
@@ -241,6 +241,14 @@
     Public Sub [Set](Key As String, Value As Object, Optional ForceReload As Boolean = False, Optional Version As McVersion = Nothing)
     Public Sub [Set](Key As String, Value As Object, Optional ForceReload As Boolean = False, Optional Version As McVersion = Nothing)
         [Set](Key, Value, SetupDict(Key), ForceReload, Version)
         [Set](Key, Value, SetupDict(Key), ForceReload, Version)
     End Sub
     End Sub
+    ''' <summary>
+    ''' 写入某个未经加密的设置项。
+    ''' 若该设置项经过了加密,则会抛出异常。
+    ''' </summary>
+    Public Sub SetSafe(Key As String, Value As Object, Optional ForceReload As Boolean = False, Optional Version As McVersion = Nothing)
+        If SetupDict(Key).Encoded Then Throw New InvalidOperationException("禁止写入加密设置项:" & Key)
+        [Set](Key, Version, ForceReload, Version)
+    End Sub
     Private Sub [Set](Key As String, Value As Object, E As SetupEntry, ForceReload As Boolean, Version As McVersion)
     Private Sub [Set](Key As String, Value As Object, E As SetupEntry, ForceReload As Boolean, Version As McVersion)
         Try
         Try
 
 
@@ -258,7 +266,7 @@
             If E.Encoded Then
             If E.Encoded Then
                 Try
                 Try
                     If Value Is Nothing Then Value = ""
                     If Value Is Nothing Then Value = ""
-                    Value = SecretEncrypt(Value, "PCL" & UniqueAddress)
+                    Value = SecretEncrypt(Value, "PCL" & Identify)
                 Catch ex As Exception
                 Catch ex As Exception
                     Log(ex, "加密设置失败:" & Key, LogLevel.Developer)
                     Log(ex, "加密设置失败:" & Key, LogLevel.Developer)
                 End Try
                 End Try
@@ -303,9 +311,17 @@
     ''' 获取某个设置项的值。
     ''' 获取某个设置项的值。
     ''' </summary>
     ''' </summary>
     Public Function [Get](Key As String, Optional Version As McVersion = Nothing)
     Public Function [Get](Key As String, Optional Version As McVersion = Nothing)
-        If Not SetupDict.ContainsKey(Key) Then Throw New KeyNotFoundException("未找到设置项:" & Key) With {.Source = Key}
+        If Not SetupDict.ContainsKey(Key) Then Throw New KeyNotFoundException("未找到设置项:" & Key)
         Return [Get](Key, SetupDict(Key), Version)
         Return [Get](Key, SetupDict(Key), Version)
     End Function
     End Function
+    ''' <summary>
+    ''' 获取某个未经加密的设置项的值。
+    ''' 若该设置项经过了加密,则会抛出异常。
+    ''' </summary>
+    Public Function GetSafe(Key As String, Optional Version As McVersion = Nothing)
+        If SetupDict(Key).Encoded Then Throw New InvalidOperationException("禁止读取加密设置项:" & Key)
+        Return [Get](Key, Version)
+    End Function
     Private Function [Get](Key As String, E As SetupEntry, Version As McVersion)
     Private Function [Get](Key As String, E As SetupEntry, Version As McVersion)
         '获取强制值
         '获取强制值
         Dim Force As String = ForceValue(Key)
         Dim Force As String = ForceValue(Key)
@@ -383,7 +399,7 @@
                     SourceValue = E.DefaultValue
                     SourceValue = E.DefaultValue
                 Else
                 Else
                     Try
                     Try
-                        SourceValue = SecretDecrypt(SourceValue, "PCL" & UniqueAddress)
+                        SourceValue = SecretDecrypt(SourceValue, "PCL" & Identify)
                     Catch ex As Exception
                     Catch ex As Exception
                         Log(ex, "解密设置失败:" & Key, LogLevel.Developer)
                         Log(ex, "解密设置失败:" & Key, LogLevel.Developer)
                         SourceValue = E.DefaultValue
                         SourceValue = E.DefaultValue
@@ -601,8 +617,7 @@
                 FrmSetupUI.HintCustom.Visibility = Visibility.Visible
                 FrmSetupUI.HintCustom.Visibility = Visibility.Visible
                 FrmSetupUI.HintCustomWarn.Visibility = If(Setup.Get("HintCustomWarn"), Visibility.Collapsed, Visibility.Visible)
                 FrmSetupUI.HintCustomWarn.Visibility = If(Setup.Get("HintCustomWarn"), Visibility.Collapsed, Visibility.Visible)
                 FrmSetupUI.HintCustom.Text = $"从 PCL 文件夹下的 Custom.xaml 读取主页内容。{vbCrLf}你可以手动编辑该文件,向主页添加文本、图片、常用网站、快捷启动等功能。"
                 FrmSetupUI.HintCustom.Text = $"从 PCL 文件夹下的 Custom.xaml 读取主页内容。{vbCrLf}你可以手动编辑该文件,向主页添加文本、图片、常用网站、快捷启动等功能。"
-                FrmSetupUI.HintCustom.EventType = ""
-                FrmSetupUI.HintCustom.EventData = ""
+                CustomEventService.SetEventType(FrmSetupUI.HintCustom, CustomEvent.EventType.None)
             Case 2 '联网
             Case 2 '联网
                 FrmSetupUI.PanCustomPreset.Visibility = Visibility.Collapsed
                 FrmSetupUI.PanCustomPreset.Visibility = Visibility.Collapsed
                 FrmSetupUI.PanCustomLocal.Visibility = Visibility.Collapsed
                 FrmSetupUI.PanCustomLocal.Visibility = Visibility.Collapsed
@@ -610,8 +625,8 @@
                 FrmSetupUI.HintCustom.Visibility = Visibility.Visible
                 FrmSetupUI.HintCustom.Visibility = Visibility.Visible
                 FrmSetupUI.HintCustomWarn.Visibility = If(Setup.Get("HintCustomWarn"), Visibility.Collapsed, Visibility.Visible)
                 FrmSetupUI.HintCustomWarn.Visibility = If(Setup.Get("HintCustomWarn"), Visibility.Collapsed, Visibility.Visible)
                 FrmSetupUI.HintCustom.Text = $"从指定网址联网获取主页内容。服主也可以用于动态更新服务器公告。{vbCrLf}如果你制作了稳定运行的联网主页,可以点击这条提示投稿,若合格即可加入预设!"
                 FrmSetupUI.HintCustom.Text = $"从指定网址联网获取主页内容。服主也可以用于动态更新服务器公告。{vbCrLf}如果你制作了稳定运行的联网主页,可以点击这条提示投稿,若合格即可加入预设!"
-                FrmSetupUI.HintCustom.EventType = "打开网页"
-                FrmSetupUI.HintCustom.EventData = "https://github.com/Meloong-Git/PCL/discussions/2528"
+                CustomEventService.SetEventType(FrmSetupUI.HintCustom, CustomEvent.EventType.打开网页)
+                CustomEventService.SetEventData(FrmSetupUI.HintCustom, "https://github.com/Meloong-Git/PCL/discussions/2528")
             Case 3 '预设
             Case 3 '预设
                 FrmSetupUI.PanCustomPreset.Visibility = Visibility.Visible
                 FrmSetupUI.PanCustomPreset.Visibility = Visibility.Visible
                 FrmSetupUI.PanCustomLocal.Visibility = Visibility.Collapsed
                 FrmSetupUI.PanCustomLocal.Visibility = Visibility.Collapsed

+ 5 - 5
Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml

@@ -41,9 +41,9 @@
                             <local:MyComboBoxItem Content="隔离所有版本" ToolTip="不同版本之间的存档、Mod、资源包等均不互通。&#xa;这会导致不同原版版本间的存档不能共用,所以一般不推荐。" />
                             <local:MyComboBoxItem Content="隔离所有版本" ToolTip="不同版本之间的存档、Mod、资源包等均不互通。&#xa;这会导致不同原版版本间的存档不能共用,所以一般不推荐。" />
                         </local:MyComboBox>
                         </local:MyComboBox>
                         <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="游戏窗口标题" Margin="0,0,25,0" />
                         <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="游戏窗口标题" Margin="0,0,25,0" />
-                        <local:MyTextBox x:Name="TextArgumentTitle" Grid.Row="2" Grid.ColumnSpan="2" Tag="LaunchArgumentTitle" Grid.Column="1" ToolTip="自定义游戏窗口的标题,若留空则不进行更改。&#xa;支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {name}:游戏版本名&#xa; · {date}、{time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号" HintText="默认" MaxLength="100" />
+                        <local:MyTextBox x:Name="TextArgumentTitle" Grid.Row="2" Grid.ColumnSpan="2" Tag="LaunchArgumentTitle" Grid.Column="1" ToolTip="自定义游戏窗口的标题,若留空则不进行更改。&#xa;你可以用替换标记来动态改变其内容,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。" HintText="默认" MaxLength="100" />
                         <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="自定义信息" Margin="0,0,25,0" />
                         <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="自定义信息" Margin="0,0,25,0" />
-                        <local:MyTextBox x:Name="TextArgumentInfo" Grid.Row="4" Grid.ColumnSpan="2" Tag="LaunchArgumentInfo" Text="PCL" Grid.Column="1" ToolTip="该信息会显示在游戏主界面的左下角,与 F3 调试页面的左上角">
+                        <local:MyTextBox x:Name="TextArgumentInfo" Grid.Row="4" Grid.ColumnSpan="2" Tag="LaunchArgumentInfo" Text="PCL" Grid.Column="1" ToolTip="该信息会显示在游戏主界面的左下角,与 F3 调试页面的左上角。&#xa;你可以用替换标记来动态改变其内容,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。">
                             <local:MyTextBox.ValidateRules>
                             <local:MyTextBox.ValidateRules>
                                 <local:ValidateExcept>
                                 <local:ValidateExcept>
                                     <local:ValidateExcept.Excepts>
                                     <local:ValidateExcept.Excepts>
@@ -240,13 +240,13 @@
                                         ToolTip="还原" ToolTipService.Placement="Right" ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
                                         ToolTip="还原" ToolTipService.Placement="Right" ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
                                         Logo="M530 0c287 0 521 229 521 511s-233 511-521 511c-233 0-436-151-500-368a63 63 0 0 1 44-79 65 65 0 0 1 80 43c48 162 200 276 375 276 215 0 390-171 390-383s-174-383-390-383c-103 0-199 39-270 106l21-5a63 63 0 0 1 33 123l-157 42a65 65 0 0 1-90-42l-49-183a65 65 0 1 1 126-33l6 26A524 524 0 0 1 530 0z" LogoScale="0.9" />
                                         Logo="M530 0c287 0 521 229 521 511s-233 511-521 511c-233 0-436-151-500-368a63 63 0 0 1 44-79 65 65 0 0 1 80 43c48 162 200 276 375 276 215 0 390-171 390-383s-174-383-390-383c-103 0-199 39-270 106l21-5a63 63 0 0 1 33 123l-157 42a65 65 0 0 1-90-42l-49-183a65 65 0 1 1 126-33l6 26A524 524 0 0 1 530 0z" LogoScale="0.9" />
                     </StackPanel>
                     </StackPanel>
-                    <local:MyTextBox x:Name="TextAdvanceJvm" Grid.ColumnSpan="2" Tag="LaunchAdvanceJvm" Grid.Column="1" ToolTip="启动 Minecraft 时使用的额外 JVM 参数,除非有确定把握,否则请不要修改。&#xa;支持 Minecraft 版本 JSON 中的字符串替换标记,例如 ${library_directory}。" MaxLength="4000"
+                    <local:MyTextBox x:Name="TextAdvanceJvm" Grid.ColumnSpan="2" Tag="LaunchAdvanceJvm" Grid.Column="1" ToolTip="启动 Minecraft 时使用的额外 JVM 参数,除非有确定把握,否则请不要修改。&#xa;&#xa;支持 Minecraft 版本 JSON 中的字符串替换标记,例如 ${library_directory}。&#xa;也支持使用 PCL 的替换标记,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。" MaxLength="4000"
                                  Height="100" AcceptsReturn="True" TextWrapping="Wrap" VerticalContentAlignment="Top" Padding="0,5" VerticalScrollBarVisibility="Auto" />
                                  Height="100" AcceptsReturn="True" TextWrapping="Wrap" VerticalContentAlignment="Top" Padding="0,5" VerticalScrollBarVisibility="Auto" />
                     <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="游戏参数" Margin="0,0,25,0" />
                     <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="游戏参数" Margin="0,0,25,0" />
-                    <local:MyTextBox x:Name="TextAdvanceGame" Grid.Row="2" Grid.ColumnSpan="2" Tag="LaunchAdvanceGame" Grid.Column="1" ToolTip="文本框中的内容将会被直接拼合在启动参数的末尾。&#xa;例如,输入 --demo 则会以试玩模式启动游戏。&#xa;支持 Minecraft 版本 JSON 中的字符串替换标记,例如 ${library_directory}。" />
+                    <local:MyTextBox x:Name="TextAdvanceGame" Grid.Row="2" Grid.ColumnSpan="2" Tag="LaunchAdvanceGame" Grid.Column="1" ToolTip="文本框中的内容将会被直接拼合在启动参数的末尾。&#xa;例如,输入 --demo 则会以试玩模式启动游戏。&#xa;&#xa;支持 Minecraft 版本 JSON 中的字符串替换标记,例如 ${library_directory}。&#xa;也支持使用 PCL 的替换标记,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。" />
                     <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="启动前执行命令" Margin="0,0,25,0" />
                     <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="启动前执行命令" Margin="0,0,25,0" />
                     <local:MyTextBox x:Name="TextAdvanceRun" Grid.Row="4" Grid.ColumnSpan="2" Tag="LaunchAdvanceRun" Grid.Column="1" HintText=""
                     <local:MyTextBox x:Name="TextAdvanceRun" Grid.Row="4" Grid.ColumnSpan="2" Tag="LaunchAdvanceRun" Grid.Column="1" HintText=""
-                                 ToolTip="在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xa;&#xa;可以使用以下替换标记实现相对路径(路径均以 \ 结尾):&#xa; · {path}:PCL 的 exe 文件所在的文件夹&#xa; · {minecraft}:.minecraft 文件夹&#xa; · {verpath}:版本文件夹(.minecraft\versions\版本名\)&#xa; · {verindie}:开启版本隔离时等同版本文件夹,未开启时等同 .minecraft 文件夹&#xa; · {java}:游戏运行时的 Java 文件夹&#xa;&#xa;除此之外,也支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {uuid}:玩家的 UUID&#xa; · {name}:游戏版本名&#xa; · {date}、{time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号&#xa;&#xa;例如:&#xa; · &quot;{verpath}test.exe&quot; :运行版本文件夹下的 test.exe 程序&#xa; · &quot;{java}java.exe&quot; -jar &quot;{verpath}test.jar&quot; :用 Java 运行版本文件夹下的 test.jar&#xa; · notepad &quot;{verindie}options.txt&quot; :使用记事本打开该版本的设置文件&#xa;&#xa;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。&#xa;执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。" />
+                                 ToolTip="在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xa;&#xa;你可以用替换标记来动态改变其内容或实现相对路径,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。例如:&#xa; · &quot;{verpath}test.exe&quot; :运行版本文件夹下的 test.exe 程序&#xa; · &quot;{java}java.exe&quot; -jar &quot;{verpath}test.jar&quot; :用 Java 运行版本文件夹下的 test.jar&#xa; · notepad &quot;{verindie}options.txt&quot; :使用记事本打开该版本的设置文件&#xa;&#xa;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。&#xa;执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。" />
                     <local:MyCheckBox Grid.Row="5" Grid.Column="1" x:Name="CheckAdvanceRunWait" Tag="LaunchAdvanceRunWait" Visibility="Collapsed"
                     <local:MyCheckBox Grid.Row="5" Grid.Column="1" x:Name="CheckAdvanceRunWait" Tag="LaunchAdvanceRunWait" Visibility="Collapsed"
                                   Text="等待命令执行完成后再继续启动" Margin="0,8,0,7" />
                                   Text="等待命令执行完成后再继续启动" Margin="0,8,0,7" />
                     <StackPanel Margin="0,12,0,4" Grid.Row="6" Grid.ColumnSpan="2" HorizontalAlignment="Left">
                     <StackPanel Margin="0,12,0,4" Grid.Row="6" Grid.ColumnSpan="2" HorizontalAlignment="Left">

+ 7 - 7
Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb

@@ -93,7 +93,7 @@
             JavaSearchLoader.Start(IsForceRestart:=True)
             JavaSearchLoader.Start(IsForceRestart:=True)
 
 
             Log("[Setup] 已初始化启动设置")
             Log("[Setup] 已初始化启动设置")
-            Hint("已初始化启动设置!", HintType.Finish, False)
+            Hint("已初始化启动设置!", HintType.Green, False)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "初始化启动设置失败", LogLevel.Msgbox)
             Log(ex, "初始化启动设置失败", LogLevel.Msgbox)
         End Try
         End Try
@@ -170,7 +170,7 @@
         Try
         Try
             File.Delete(PathAppdata & "CustomSkin.png")
             File.Delete(PathAppdata & "CustomSkin.png")
             RadioSkinType0.SetChecked(True, True)
             RadioSkinType0.SetChecked(True, True)
-            Hint("离线皮肤已清空!", HintType.Finish)
+            Hint("离线皮肤已清空!", HintType.Green)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "清空离线皮肤失败", LogLevel.Msgbox)
             Log(ex, "清空离线皮肤失败", LogLevel.Msgbox)
         End Try
         End Try
@@ -468,7 +468,7 @@ PreFin:
     '手动选择
     '手动选择
     Private Sub BtnArgumentJavaSelect_Click(sender As Object, e As EventArgs) Handles BtnArgumentJavaSelect.Click
     Private Sub BtnArgumentJavaSelect_Click(sender As Object, e As EventArgs) Handles BtnArgumentJavaSelect.Click
         If JavaSearchLoader.State = LoadState.Loading Then
         If JavaSearchLoader.State = LoadState.Loading Then
-            Hint("正在搜索 Java,请稍候!", HintType.Critical)
+            Hint("正在搜索 Java,请稍候!", HintType.Red)
             Return
             Return
         End If
         End If
         '选择 Java
         '选择 Java
@@ -489,7 +489,7 @@ PreFin:
             Setup.Set("LaunchArgumentJavaAll", JavaNewList.ToString(Newtonsoft.Json.Formatting.None))
             Setup.Set("LaunchArgumentJavaAll", JavaNewList.ToString(Newtonsoft.Json.Formatting.None))
             '重新加载列表
             '重新加载列表
             JavaSearchLoader.Start(IsForceRestart:=True)
             JavaSearchLoader.Start(IsForceRestart:=True)
-            Hint("已将该 Java 加入 Java 列表!", HintType.Finish)
+            Hint("已将该 Java 加入 Java 列表!", HintType.Green)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "该 Java 存在异常,无法使用", LogLevel.Msgbox, "异常的 Java")
             Log(ex, "该 Java 存在异常,无法使用", LogLevel.Msgbox, "异常的 Java")
             Return
             Return
@@ -498,7 +498,7 @@ PreFin:
     '自动查找
     '自动查找
     Private Sub BtnArgumentJavaSearch_Click(sender As Object, e As EventArgs) Handles BtnArgumentJavaSearch.Click
     Private Sub BtnArgumentJavaSearch_Click(sender As Object, e As EventArgs) Handles BtnArgumentJavaSearch.Click
         If JavaSearchLoader.State = LoadState.Loading Then
         If JavaSearchLoader.State = LoadState.Loading Then
-            Hint("正在搜索 Java,请稍候!", HintType.Critical)
+            Hint("正在搜索 Java,请稍候!", HintType.Red)
             Return
             Return
         End If
         End If
         RunInThread(
         RunInThread(
@@ -506,9 +506,9 @@ PreFin:
             Hint("正在搜索 Java!")
             Hint("正在搜索 Java!")
             JavaSearchLoader.WaitForExit(IsForceRestart:=True)
             JavaSearchLoader.WaitForExit(IsForceRestart:=True)
             If Not JavaList.Any() Then
             If Not JavaList.Any() Then
-                Hint("未找到可用的 Java!", HintType.Critical)
+                Hint("未找到可用的 Java!", HintType.Red)
             Else
             Else
-                Hint("已找到 " & JavaList.Count & " 个 Java,请检查下拉框查看列表!", HintType.Finish)
+                Hint("已找到 " & JavaList.Count & " 个 Java,请检查下拉框查看列表!", HintType.Green)
             End If
             End If
         End Sub)
         End Sub)
     End Sub
     End Sub

+ 1 - 1
Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb

@@ -28,7 +28,7 @@
             Setup.Reset("LinkHiperCertWarn")
             Setup.Reset("LinkHiperCertWarn")
 
 
             Log("[Setup] 已初始化联机页设置")
             Log("[Setup] 已初始化联机页设置")
-            Hint("已初始化联机页设置!", HintType.Finish, False)
+            Hint("已初始化联机页设置!", HintType.Green, False)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "初始化联机页设置失败", LogLevel.Msgbox)
             Log(ex, "初始化联机页设置失败", LogLevel.Msgbox)
         End Try
         End Try

+ 2 - 2
Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb

@@ -85,7 +85,7 @@
             Setup.Reset("SystemSystemTelemetry")
             Setup.Reset("SystemSystemTelemetry")
 
 
             Log("[Setup] 已初始化启动器页设置")
             Log("[Setup] 已初始化启动器页设置")
-            Hint("已初始化启动器页设置!", HintType.Finish, False)
+            Hint("已初始化启动器页设置!", HintType.Green, False)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "初始化启动器页设置失败", LogLevel.Msgbox)
             Log(ex, "初始化启动器页设置失败", LogLevel.Msgbox)
         End Try
         End Try
@@ -136,7 +136,7 @@
 
 
     '识别码/解锁码替代入口
     '识别码/解锁码替代入口
     Private Sub BtnSystemIdentify_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnSystemIdentify.Click
     Private Sub BtnSystemIdentify_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnSystemIdentify.Click
-        PageOtherAbout.CopyUniqueAddress()
+        PageOtherAbout.CopyIdentify()
     End Sub
     End Sub
     Private Sub BtnSystemUnlock_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnSystemUnlock.Click
     Private Sub BtnSystemUnlock_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnSystemUnlock.Click
         DonateCodeInput()
         DonateCodeInput()

+ 2 - 1
Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml

@@ -225,7 +225,8 @@
                             <ColumnDefinition />
                             <ColumnDefinition />
                         </Grid.ColumnDefinitions>
                         </Grid.ColumnDefinitions>
                         <TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Text="下载地址" Margin="0,0,25,0" />
                         <TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Text="下载地址" Margin="0,0,25,0" />
-                        <local:MyTextBox x:Name="TextCustomNet" Tag="UiCustomNet" Grid.Column="1" MaxHeight="28">
+                        <local:MyTextBox x:Name="TextCustomNet" Tag="UiCustomNet" Grid.Column="1" MaxHeight="28"
+                                         ToolTip="你可以用替换标记动态改变网址,以传入参数或变量。&#xa;在 更多 → 帮助 中搜索 “替换标记” 以了解用法。">
                             <local:MyTextBox.ValidateRules>
                             <local:MyTextBox.ValidateRules>
                                 <local:ValidateNullable />
                                 <local:ValidateNullable />
                                 <local:ValidateHttp />
                                 <local:ValidateHttp />

+ 9 - 9
Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb

@@ -162,7 +162,7 @@
             Setup.Reset("UiHiddenOtherTest")
             Setup.Reset("UiHiddenOtherTest")
 
 
             Log("[Setup] 已初始化个性化设置!")
             Log("[Setup] 已初始化个性化设置!")
-            Hint("已初始化个性化设置", HintType.Finish, False)
+            Hint("已初始化个性化设置", HintType.Green, False)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "初始化个性化设置失败", LogLevel.Msgbox)
             Log(ex, "初始化个性化设置失败", LogLevel.Msgbox)
         End Try
         End Try
@@ -215,7 +215,7 @@
         If MyMsgBox("即将删除背景图片文件夹中的所有文件。" & vbCrLf & "此操作不可撤销,是否确定?", "警告",, "取消", IsWarn:=True) = 1 Then
         If MyMsgBox("即将删除背景图片文件夹中的所有文件。" & vbCrLf & "此操作不可撤销,是否确定?", "警告",, "取消", IsWarn:=True) = 1 Then
             DeleteDirectory(Path & "PCL\Pictures")
             DeleteDirectory(Path & "PCL\Pictures")
             BackgroundRefresh(False, True)
             BackgroundRefresh(False, True)
-            Hint("背景图片已清空!", HintType.Finish)
+            Hint("背景图片已清空!", HintType.Green)
         End If
         End If
     End Sub
     End Sub
     ''' <summary>
     ''' <summary>
@@ -238,10 +238,10 @@
             If Not Pic.Any() Then
             If Not Pic.Any() Then
                 If Refresh Then
                 If Refresh Then
                     If FrmMain.ImgBack.Visibility = Visibility.Collapsed Then
                     If FrmMain.ImgBack.Visibility = Visibility.Collapsed Then
-                        If IsHint Then Hint("未检测到可用背景图片!", HintType.Critical)
+                        If IsHint Then Hint("未检测到可用背景图片!", HintType.Red)
                     Else
                     Else
                         FrmMain.ImgBack.Visibility = Visibility.Collapsed
                         FrmMain.ImgBack.Visibility = Visibility.Collapsed
-                        If IsHint Then Hint("背景图片已清除!", HintType.Finish)
+                        If IsHint Then Hint("背景图片已清除!", HintType.Green)
                     End If
                     End If
                 End If
                 End If
                 If Not IsNothing(FrmSetupUI) Then FrmSetupUI.BackgroundRefreshUI(False, 0)
                 If Not IsNothing(FrmSetupUI) Then FrmSetupUI.BackgroundRefreshUI(False, 0)
@@ -253,7 +253,7 @@
                         FrmMain.ImgBack.Background = New MyBitmap(Address)
                         FrmMain.ImgBack.Background = New MyBitmap(Address)
                         Setup.Load("UiBackgroundSuit", True)
                         Setup.Load("UiBackgroundSuit", True)
                         FrmMain.ImgBack.Visibility = Visibility.Visible
                         FrmMain.ImgBack.Visibility = Visibility.Visible
-                        If IsHint Then Hint("背景图片已刷新:" & GetFileNameFromPath(Address), HintType.Finish, False)
+                        If IsHint Then Hint("背景图片已刷新:" & GetFileNameFromPath(Address), HintType.Green, False)
                     Catch ex As Exception
                     Catch ex As Exception
                         If ex.Message.Contains("参数无效") Then
                         If ex.Message.Contains("参数无效") Then
                             Log("刷新背景图片失败,该图片文件可能并非标准格式。" & vbCrLf &
                             Log("刷新背景图片失败,该图片文件可能并非标准格式。" & vbCrLf &
@@ -338,7 +338,7 @@ Refresh:
         Try
         Try
             File.Delete(Path & "PCL\Logo.png")
             File.Delete(Path & "PCL\Logo.png")
             RadioLogoType1.SetChecked(True, True)
             RadioLogoType1.SetChecked(True, True)
-            Hint("标题栏图片已清空!", HintType.Finish)
+            Hint("标题栏图片已清空!", HintType.Green)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "清空标题栏图片失败", LogLevel.Msgbox)
             Log(ex, "清空标题栏图片失败", LogLevel.Msgbox)
         End Try
         End Try
@@ -381,7 +381,7 @@ Refresh:
                 '删除文件
                 '删除文件
                 Try
                 Try
                     DeleteDirectory(Path & "PCL\Musics")
                     DeleteDirectory(Path & "PCL\Musics")
-                    Hint("背景音乐已删除!", HintType.Finish)
+                    Hint("背景音乐已删除!", HintType.Green)
                 Catch ex As Exception
                 Catch ex As Exception
                     Log(ex, "删除背景音乐失败", LogLevel.Msgbox)
                     Log(ex, "删除背景音乐失败", LogLevel.Msgbox)
                 End Try
                 End Try
@@ -410,7 +410,7 @@ Refresh:
                 If MyMsgBox("当前已存在布局文件,继续生成教学文件将会覆盖现有布局文件!", "覆盖确认", "继续", "取消", IsWarn:=True) = 2 Then Return
                 If MyMsgBox("当前已存在布局文件,继续生成教学文件将会覆盖现有布局文件!", "覆盖确认", "继续", "取消", IsWarn:=True) = 2 Then Return
             End If
             End If
             WriteFile(Path & "PCL\Custom.xaml", GetResources("Custom"))
             WriteFile(Path & "PCL\Custom.xaml", GetResources("Custom"))
-            Hint("教学文件已生成!", HintType.Finish)
+            Hint("教学文件已生成!", HintType.Green)
             OpenExplorer(Path & "PCL\Custom.xaml")
             OpenExplorer(Path & "PCL\Custom.xaml")
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "生成教学文件失败", LogLevel.Feedback)
             Log(ex, "生成教学文件失败", LogLevel.Feedback)
@@ -418,7 +418,7 @@ Refresh:
     End Sub
     End Sub
     Private Sub BtnCustomRefresh_Click() Handles BtnCustomRefresh.Click
     Private Sub BtnCustomRefresh_Click() Handles BtnCustomRefresh.Click
         FrmLaunchRight.ForceRefresh()
         FrmLaunchRight.ForceRefresh()
-        Hint("已刷新主页!", HintType.Finish)
+        Hint("已刷新主页!", HintType.Green)
     End Sub
     End Sub
     Private Sub BtnCustomTutorial_Click(sender As Object, e As EventArgs) Handles BtnCustomTutorial.Click
     Private Sub BtnCustomTutorial_Click(sender As Object, e As EventArgs) Handles BtnCustomTutorial.Click
         MyMsgBox("1. 点击 生成教学文件 按钮,这会在 PCL 文件夹下生成 Custom.xaml 布局文件。" & vbCrLf &
         MyMsgBox("1. 点击 生成教学文件 按钮,这会在 PCL 文件夹下生成 Custom.xaml 布局文件。" & vbCrLf &

+ 1 - 1
Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb

@@ -96,7 +96,7 @@ Public Class PageSpeedLeft
                             AddHandler Tb.MouseLeftButtonDown,
                             AddHandler Tb.MouseLeftButtonDown,
                             Sub(sender As TextBlock, e As EventArgs)
                             Sub(sender As TextBlock, e As EventArgs)
                                 ClipboardSet(sender.Text, False)
                                 ClipboardSet(sender.Text, False)
-                                Hint("已复制错误详情!", HintType.Finish)
+                                Hint("已复制错误详情!", HintType.Green)
                             End Sub
                             End Sub
                             Card.Children.Add(Tb)
                             Card.Children.Add(Tb)
 #End Region
 #End Region

+ 1 - 1
Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml

@@ -262,7 +262,7 @@
                             <local:MyButton Grid.Column="0" x:Name="BtnAdvancedImport" MinWidth="140" Text="读取配置" Padding="13,0" Margin="0,0,20,0" ColorType="Highlight" />
                             <local:MyButton Grid.Column="0" x:Name="BtnAdvancedImport" MinWidth="140" Text="读取配置" Padding="13,0" Margin="0,0,20,0" ColorType="Highlight" />
                             <local:MyButton Grid.Column="1" x:Name="BtnAdvancedExport" MinWidth="140" Text="保存配置" Padding="13,0" Margin="0,0,20,0" />
                             <local:MyButton Grid.Column="1" x:Name="BtnAdvancedExport" MinWidth="140" Text="保存配置" Padding="13,0" Margin="0,0,20,0" />
                             <local:MyButton Grid.Column="2" MinWidth="140" Text="整合包制作指南" Padding="13,0" Margin="0,0,20,0" x:Name="BtnAdvancedHelp"
                             <local:MyButton Grid.Column="2" MinWidth="140" Text="整合包制作指南" Padding="13,0" Margin="0,0,20,0" x:Name="BtnAdvancedHelp"
-                                            EventType="打开帮助" />
+                                            local:CustomEventService.EventType="打开帮助" />
                         </Grid>
                         </Grid>
                     </StackPanel>
                     </StackPanel>
                 </local:MyCard>
                 </local:MyCard>

+ 7 - 6
Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb

@@ -22,7 +22,8 @@ Public Class PageVersionExport
     Private Sub PageVersionExport_Loaded() Handles Me.Loaded
     Private Sub PageVersionExport_Loaded() Handles Me.Loaded
         AniControlEnabled += 1
         AniControlEnabled += 1
         If CurrentVersion <> PageVersionLeft.Version.Path Then RefreshAll() '切换到了另一个版本,重置页面
         If CurrentVersion <> PageVersionLeft.Version.Path Then RefreshAll() '切换到了另一个版本,重置页面
-        BtnAdvancedHelp.EventData = If(VersionBranchName = "Release", "指南/整合包制作 - Public.json", "指南/整合包制作 - Snapshot.json")
+        CustomEventService.SetEventData(BtnAdvancedHelp,
+            If(VersionBranchName = "Release", "指南/整合包制作 - Public.json", "指南/整合包制作 - Snapshot.json"))
         AniControlEnabled -= 1
         AniControlEnabled -= 1
     End Sub
     End Sub
     Public Sub RefreshAll() Implements IRefreshable.Refresh
     Public Sub RefreshAll() Implements IRefreshable.Refresh
@@ -35,7 +36,7 @@ Public Class PageVersionExport
         TextExportVersion.HintText = "1.0.0"
         TextExportVersion.HintText = "1.0.0"
         CheckAdvancedInclude.Checked = False
         CheckAdvancedInclude.Checked = False
         CheckAdvancedModrinth.Checked = False
         CheckAdvancedModrinth.Checked = False
-        GetExportOption(CheckOptionsBasic).Description = PageVersionLeft.Version.GetDefaultDescription()
+        GetExportOption(CheckOptionsBasic).Description = PageVersionLeft.Version.GetVersionDescription()
         ResetConfigOverrides()
         ResetConfigOverrides()
         ReloadAllSubOptions()
         ReloadAllSubOptions()
         RefreshAllOptionsUI()
         RefreshAllOptionsUI()
@@ -341,7 +342,7 @@ Public Class PageVersionExport
             ConfigLines.AddRange(GetExtraFileLines())
             ConfigLines.AddRange(GetExtraFileLines())
             '结束
             '结束
             WriteFile(ConfigPath, ConfigLines.Join(vbCrLf))
             WriteFile(ConfigPath, ConfigLines.Join(vbCrLf))
-            Hint("已保存配置文件:" & ConfigPath, HintType.Finish)
+            Hint("已保存配置文件:" & ConfigPath, HintType.Green)
             OpenExplorer(ConfigPath)
             OpenExplorer(ConfigPath)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "保存配置失败", LogLevel.Msgbox)
             Log(ex, "保存配置失败", LogLevel.Msgbox)
@@ -378,7 +379,7 @@ Public Class PageVersionExport
                 ExtraFiles = Nothing
                 ExtraFiles = Nothing
             End If
             End If
             '结束
             '结束
-            Hint("已读取配置文件:" & ConfigPath, HintType.Finish)
+            Hint("已读取配置文件:" & ConfigPath, HintType.Green)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "读取配置失败", LogLevel.Msgbox)
             Log(ex, "读取配置失败", LogLevel.Msgbox)
         End Try
         End Try
@@ -517,13 +518,13 @@ Public Class PageVersionExport
                     If Directory.Exists(Line) Then
                     If Directory.Exists(Line) Then
                         CopyDirectory(Line, BaseFolder & GetFolderNameFromPath(Line) & "\")
                         CopyDirectory(Line, BaseFolder & GetFolderNameFromPath(Line) & "\")
                     Else
                     Else
-                        Hint($"未找到配置文件中指定的文件夹:{Line}", HintType.Critical)
+                        Hint($"未找到配置文件中指定的文件夹:{Line}", HintType.Red)
                     End If
                     End If
                 Else
                 Else
                     If File.Exists(Line) Then
                     If File.Exists(Line) Then
                         CopyFile(Line, BaseFolder & GetFileNameFromPath(Line))
                         CopyFile(Line, BaseFolder & GetFileNameFromPath(Line))
                     Else
                     Else
-                        Hint($"未找到配置文件中指定的单个文件:{Line}", HintType.Critical)
+                        Hint($"未找到配置文件中指定的单个文件:{Line}", HintType.Red)
                     End If
                     End If
                 End If
                 End If
             Next
             Next

+ 17 - 17
Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb

@@ -306,7 +306,7 @@
             If Result.Any Then
             If Result.Any Then
                 MyMsgBox(Join(Result, vbCrLf & vbCrLf), "Mod 检查结果")
                 MyMsgBox(Join(Result, vbCrLf & vbCrLf), "Mod 检查结果")
             Else
             Else
-                Hint("Mod 检查完成,未发现任何问题!", HintType.Finish)
+                Hint("Mod 检查完成,未发现任何问题!", HintType.Green)
             End If
             End If
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "进行 Mod 检查时出错", LogLevel.Feedback)
             Log(ex, "进行 Mod 检查时出错", LogLevel.Feedback)
@@ -340,7 +340,7 @@
         Log("[System] 文件为 jar/litemod 格式,尝试作为 Mod 安装")
         Log("[System] 文件为 jar/litemod 格式,尝试作为 Mod 安装")
         '检查回收站:回收站中的文件有错误的文件名
         '检查回收站:回收站中的文件有错误的文件名
         If FilePathList.First.Contains(":\$RECYCLE.BIN\") Then
         If FilePathList.First.Contains(":\$RECYCLE.BIN\") Then
-            Hint("请先将文件从回收站还原,再尝试安装!", HintType.Critical)
+            Hint("请先将文件从回收站还原,再尝试安装!", HintType.Red)
             Return True
             Return True
         End If
         End If
         '获取并检查目标版本
         '获取并检查目标版本
@@ -362,9 +362,9 @@ Install:
                     CopyFile(ModFile, TargetVersion.PathIndie & "mods\" & NewFileName)
                     CopyFile(ModFile, TargetVersion.PathIndie & "mods\" & NewFileName)
                 Next
                 Next
                 If FilePathList.Count = 1 Then
                 If FilePathList.Count = 1 Then
-                    Hint($"已安装 {GetFileNameFromPath(FilePathList.First).Replace(".disabled", "").Replace(".old", "")}!", HintType.Finish)
+                    Hint($"已安装 {GetFileNameFromPath(FilePathList.First).Replace(".disabled", "").Replace(".old", "")}!", HintType.Green)
                 Else
                 Else
-                    Hint($"已安装 {FilePathList.Count} 个 Mod!", HintType.Finish)
+                    Hint($"已安装 {FilePathList.Count} 个 Mod!", HintType.Green)
                 End If
                 End If
                 '刷新列表
                 '刷新列表
                 If FrmMain.PageCurrent = FormMain.PageType.VersionSetup AndAlso FrmMain.PageCurrentSub = FormMain.PageSubType.VersionMod Then
                 If FrmMain.PageCurrent = FormMain.PageType.VersionSetup AndAlso FrmMain.PageCurrentSub = FormMain.PageSubType.VersionMod Then
@@ -570,7 +570,7 @@ Install:
         If IsSuccessful Then
         If IsSuccessful Then
             RefreshBars()
             RefreshBars()
         Else
         Else
-            Hint("由于文件被占用,Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical)
+            Hint("由于文件被占用,Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Red)
             ReloadModList(True)
             ReloadModList(True)
         End If
         End If
         LoaderRun(LoaderFolderRunType.UpdateOnly)
         LoaderRun(LoaderFolderRunType.UpdateOnly)
@@ -643,14 +643,14 @@ Install:
                 Try
                 Try
                     For Each Entry As McMod In ModList
                     For Each Entry As McMod In ModList
                         If File.Exists(Entry.Path) Then
                         If File.Exists(Entry.Path) Then
-                            My.Computer.FileSystem.DeleteFile(Entry.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
+                            My.Computer.FileSystem.DeleteFile(Entry.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
                         Else
                         Else
                             Log($"[Mod] 未找到更新前的 Mod 文件,跳过对它的删除:{Entry.Path}", LogLevel.Debug)
                             Log($"[Mod] 未找到更新前的 Mod 文件,跳过对它的删除:{Entry.Path}", LogLevel.Debug)
                         End If
                         End If
                     Next
                     Next
                     For Each Entry As KeyValuePair(Of String, String) In FileCopyList
                     For Each Entry As KeyValuePair(Of String, String) In FileCopyList
                         If File.Exists(Entry.Value) Then
                         If File.Exists(Entry.Value) Then
-                            My.Computer.FileSystem.DeleteFile(Entry.Value, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
+                            My.Computer.FileSystem.DeleteFile(Entry.Value, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
                             Log($"[Mod] 更新后的 Mod 文件已存在,将会把它放入回收站:{Entry.Value}", LogLevel.Debug)
                             Log($"[Mod] 更新后的 Mod 文件已存在,将会把它放入回收站:{Entry.Value}", LogLevel.Debug)
                         End If
                         End If
                         If Directory.Exists(GetPathFromFullPath(Entry.Value)) Then
                         If Directory.Exists(GetPathFromFullPath(Entry.Value)) Then
@@ -676,14 +676,14 @@ Install:
                             Case 0 '一般是由于 Mod 文件被占用,然后玩家主动取消
                             Case 0 '一般是由于 Mod 文件被占用,然后玩家主动取消
                                 Log($"[Mod] 没有 Mod 被成功更新")
                                 Log($"[Mod] 没有 Mod 被成功更新")
                             Case 1
                             Case 1
-                                Hint($"已成功更新 {FinishedFileNames.Single}!", HintType.Finish)
+                                Hint($"已成功更新 {FinishedFileNames.Single}!", HintType.Green)
                             Case Else
                             Case Else
-                                Hint($"已成功更新 {FinishedFileNames.Count} 个 Mod!", HintType.Finish)
+                                Hint($"已成功更新 {FinishedFileNames.Count} 个 Mod!", HintType.Green)
                         End Select
                         End Select
                     Case LoadState.Failed
                     Case LoadState.Failed
-                        Hint("Mod 更新失败:" & Loader.Error.GetBrief(), HintType.Critical)
+                        Hint("Mod 更新失败:" & Loader.Error.GetBrief(), HintType.Red)
                     Case LoadState.Aborted
                     Case LoadState.Aborted
-                        Hint("Mod 更新已中止!", HintType.Info)
+                        Hint("Mod 更新已中止!", HintType.Blue)
                     Case Else
                     Case Else
                         Return
                         Return
                 End Select
                 End Select
@@ -739,7 +739,7 @@ Install:
                     If IsShiftPressed Then
                     If IsShiftPressed Then
                         File.Delete(ModEntity.Path)
                         File.Delete(ModEntity.Path)
                     Else
                     Else
-                        My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
+                        My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
                     End If
                     End If
                 Catch ex As OperationCanceledException
                 Catch ex As OperationCanceledException
                     Log(ex, "删除 Mod 被主动取消")
                     Log(ex, "删除 Mod 被主动取消")
@@ -760,7 +760,7 @@ Install:
             Next
             Next
             RefreshBars()
             RefreshBars()
             If Not IsSuccessful Then
             If Not IsSuccessful Then
-                Hint("由于文件被占用,Mod 删除失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical)
+                Hint("由于文件被占用,Mod 删除失败,请尝试关闭正在运行的游戏后再试!", HintType.Red)
                 ReloadModList(True)
                 ReloadModList(True)
             ElseIf PanList.Children.Count = 0 Then
             ElseIf PanList.Children.Count = 0 Then
                 ReloadModList(True) '删除了全部文件
                 ReloadModList(True) '删除了全部文件
@@ -771,15 +771,15 @@ Install:
             If Not IsSuccessful Then Return
             If Not IsSuccessful Then Return
             If IsShiftPressed Then
             If IsShiftPressed Then
                 If ModList.Count = 1 Then
                 If ModList.Count = 1 Then
-                    Hint($"已彻底删除 {ModList.Single.FileName}!", HintType.Finish)
+                    Hint($"已彻底删除 {ModList.Single.FileName}!", HintType.Green)
                 Else
                 Else
-                    Hint($"已彻底删除 {ModList.Count} 个文件!", HintType.Finish)
+                    Hint($"已彻底删除 {ModList.Count} 个文件!", HintType.Green)
                 End If
                 End If
             Else
             Else
                 If ModList.Count = 1 Then
                 If ModList.Count = 1 Then
-                    Hint($"已将 {ModList.Single.FileName} 删除到回收站!", HintType.Finish)
+                    Hint($"已将 {ModList.Single.FileName} 删除到回收站!", HintType.Green)
                 Else
                 Else
-                    Hint($"已将 {ModList.Count} 个文件删除到回收站!", HintType.Finish)
+                    Hint($"已将 {ModList.Count} 个文件删除到回收站!", HintType.Green)
                 End If
                 End If
             End If
             End If
         Catch ex As OperationCanceledException
         Catch ex As OperationCanceledException

+ 11 - 11
Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb

@@ -30,7 +30,7 @@
         BtnFolderMods.Visibility = If(PageVersionLeft.Version.Modable, Visibility.Visible, Visibility.Collapsed)
         BtnFolderMods.Visibility = If(PageVersionLeft.Version.Modable, Visibility.Visible, Visibility.Collapsed)
         '刷新版本显示
         '刷新版本显示
         PanDisplayItem.Children.Clear()
         PanDisplayItem.Children.Clear()
-        ItemVersion = PageSelectRight.McVersionListItem(PageVersionLeft.Version)
+        ItemVersion = PageVersionLeft.Version.ToListItem
         ItemVersion.IsHitTestVisible = False
         ItemVersion.IsHitTestVisible = False
         PanDisplayItem.Children.Add(ItemVersion)
         PanDisplayItem.Children.Add(ItemVersion)
         FrmMain.PageNameRefresh()
         FrmMain.PageNameRefresh()
@@ -169,7 +169,7 @@
                 Log(ex, "重命名版本 json 失败")
                 Log(ex, "重命名版本 json 失败")
             End Try
             End Try
             '刷新与提示
             '刷新与提示
-            Hint("重命名成功!", HintType.Finish)
+            Hint("重命名成功!", HintType.Green)
             PageVersionLeft.Version = New McVersion(NewName).Load()
             PageVersionLeft.Version = New McVersion(NewName).Load()
             If Not IsNothing(McVersionCurrent) AndAlso McVersionCurrent.Equals(PageVersionLeft.Version) Then WriteIni(PathMcFolder & "PCL.ini", "Version", NewName)
             If Not IsNothing(McVersionCurrent) AndAlso McVersionCurrent.Equals(PageVersionLeft.Version) Then WriteIni(PathMcFolder & "PCL.ini", "Version", NewName)
             Reload()
             Reload()
@@ -263,7 +263,7 @@
             If SavePath = "" Then Return
             If SavePath = "" Then Return
             '检查中断(等玩家选完弹窗指不定任务就结束了呢……)
             '检查中断(等玩家选完弹窗指不定任务就结束了呢……)
             If McLaunchLoader.State = LoadState.Loading Then
             If McLaunchLoader.State = LoadState.Loading Then
-                Hint("请在当前启动任务结束后再试!", HintType.Critical)
+                Hint("请在当前启动任务结束后再试!", HintType.Red)
                 Return
                 Return
             End If
             End If
             '生成脚本
             '生成脚本
@@ -284,13 +284,13 @@
         Try
         Try
             '忽略文件检查提示
             '忽略文件检查提示
             If ShouldIgnoreFileCheck(PageVersionLeft.Version) Then
             If ShouldIgnoreFileCheck(PageVersionLeft.Version) Then
-                Hint("请先关闭 [版本设置 → 设置 → 高级启动选项 → 关闭文件校验],然后再尝试补全文件!", HintType.Info)
+                Hint("请先关闭 [版本设置 → 设置 → 高级启动选项 → 关闭文件校验],然后再尝试补全文件!", HintType.Blue)
                 Return
                 Return
             End If
             End If
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar
             For Each OngoingLoader In LoaderTaskbar
                 If OngoingLoader.Name <> PageVersionLeft.Version.Name & " 文件补全" Then Continue For
                 If OngoingLoader.Name <> PageVersionLeft.Version.Name & " 文件补全" Then Continue For
-                Hint("正在处理中,请稍候!", HintType.Critical)
+                Hint("正在处理中,请稍候!", HintType.Red)
                 Return
                 Return
             Next
             Next
             '启动
             '启动
@@ -299,11 +299,11 @@
             Sub()
             Sub()
                 Select Case Loader.State
                 Select Case Loader.State
                     Case LoadState.Finished
                     Case LoadState.Finished
-                        Hint(Loader.Name & "成功!", HintType.Finish)
+                        Hint(Loader.Name & "成功!", HintType.Green)
                     Case LoadState.Failed
                     Case LoadState.Failed
-                        Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical)
+                        Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Red)
                     Case LoadState.Aborted
                     Case LoadState.Aborted
-                        Hint(Loader.Name & "已取消!", HintType.Info)
+                        Hint(Loader.Name & "已取消!", HintType.Blue)
                 End Select
                 End Select
             End Sub
             End Sub
             Loader.Start(PageVersionLeft.Version.Name)
             Loader.Start(PageVersionLeft.Version.Name)
@@ -329,10 +329,10 @@
                     IniClearCache(PageVersionLeft.Version.Path & "PCL\Setup.ini")
                     IniClearCache(PageVersionLeft.Version.Path & "PCL\Setup.ini")
                     If IsShiftPressed Then
                     If IsShiftPressed Then
                         DeleteDirectory(PageVersionLeft.Version.Path)
                         DeleteDirectory(PageVersionLeft.Version.Path)
-                        Hint("版本 " & PageVersionLeft.Version.Name & " 已永久删除!", HintType.Finish)
+                        Hint("版本 " & PageVersionLeft.Version.Name & " 已永久删除!", HintType.Green)
                     Else
                     Else
-                        FileIO.FileSystem.DeleteDirectory(PageVersionLeft.Version.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
-                        Hint("版本 " & PageVersionLeft.Version.Name & " 已删除到回收站!", HintType.Finish)
+                        FileIO.FileSystem.DeleteDirectory(PageVersionLeft.Version.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
+                        Hint("版本 " & PageVersionLeft.Version.Name & " 已删除到回收站!", HintType.Green)
                     End If
                     End If
                 Case 2
                 Case 2
                     Return
                     Return

+ 5 - 5
Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml

@@ -34,9 +34,9 @@
                                                   ToolTip="与其余关闭隔离的版本共享存档、Mod 等文件。&#xa;若存在多个安装了 Mod 的版本,可能会由于 Mod 冲突而导致崩溃。" ToolTipService.HorizontalOffset="100"/>
                                                   ToolTip="与其余关闭隔离的版本共享存档、Mod 等文件。&#xa;若存在多个安装了 Mod 的版本,可能会由于 Mod 冲突而导致崩溃。" ToolTipService.HorizontalOffset="100"/>
                         </local:MyComboBox>
                         </local:MyComboBox>
                         <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="游戏窗口标题" Margin="0,0,25,0" />
                         <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="游戏窗口标题" Margin="0,0,25,0" />
-                        <local:MyTextBox x:Name="TextArgumentTitle" Grid.Row="2" Grid.ColumnSpan="2" Tag="VersionArgumentTitle" Grid.Column="1" ToolTip="自定义游戏窗口的标题,若留空则跟随全局设置的值。&#xa;支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {name}:游戏版本名&#xa; · {date}、{time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号" HintText="跟随全局设置" MaxLength="100" />
+                        <local:MyTextBox x:Name="TextArgumentTitle" Grid.Row="2" Grid.ColumnSpan="2" Tag="VersionArgumentTitle" Grid.Column="1" ToolTip="自定义游戏窗口的标题,若留空则跟随全局设置的值。&#xa;你可以用替换标记来动态改变其内容,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。" HintText="跟随全局设置" MaxLength="100" />
                         <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="自定义信息" Margin="0,0,25,0" />
                         <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="自定义信息" Margin="0,0,25,0" />
-                        <local:MyTextBox x:Name="TextArgumentInfo" Grid.Row="4" Grid.ColumnSpan="2" Tag="VersionArgumentInfo" Grid.Column="1" HintText="跟随全局设置" ToolTip="该信息会显示在游戏主界面的左下角,与 F3 调试页面的左上角。&#xa;若留空,则跟随全局设置的值。">
+                        <local:MyTextBox x:Name="TextArgumentInfo" Grid.Row="4" Grid.ColumnSpan="2" Tag="VersionArgumentInfo" Grid.Column="1" HintText="跟随全局设置" ToolTip="该信息会显示在游戏主界面的左下角,与 F3 调试页面的左上角。&#xa;若留空,则跟随全局设置的值。&#xa;&#xa;你可以用替换标记来动态改变其内容,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。">
                             <local:MyTextBox.ValidateRules>
                             <local:MyTextBox.ValidateRules>
                                 <local:ValidateExcept>
                                 <local:ValidateExcept>
                                     <local:ValidateExcept.Excepts>
                                     <local:ValidateExcept.Excepts>
@@ -209,13 +209,13 @@
                         <RowDefinition Height="Auto" />
                         <RowDefinition Height="Auto" />
                     </Grid.RowDefinitions>
                     </Grid.RowDefinitions>
                     <TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="Java 虚拟机参数" Margin="0,0,25,0" />
                     <TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="Java 虚拟机参数" Margin="0,0,25,0" />
-                    <local:MyTextBox x:Name="TextAdvanceJvm" Grid.Row="0" Grid.ColumnSpan="2" Tag="VersionAdvanceJvm" Grid.Column="1" MaxLength="4000" ToolTip="启动 Minecraft 时使用的额外 JVM 参数,在没有确定把握的情况下请不要尝试修改。&#xa;支持 Minecraft 版本 JSON 中的字符串替换标记,例如 ${library_directory}。&#xa;&#xa;若留空,则跟随全局设置的值。" HintText="跟随全局设置"
+                    <local:MyTextBox x:Name="TextAdvanceJvm" Grid.Row="0" Grid.ColumnSpan="2" Tag="VersionAdvanceJvm" Grid.Column="1" MaxLength="4000" ToolTip="启动 Minecraft 时使用的额外 JVM 参数,在没有确定把握的情况下请不要尝试修改。&#xa;&#xa;支持 Minecraft 版本 JSON 中的字符串替换标记,例如 ${library_directory}。&#xa;也支持使用 PCL 的替换标记,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。&#xa;&#xa;若留空,则跟随全局设置的值。" HintText="跟随全局设置"
                                      Height="100" AcceptsReturn="True" TextWrapping="Wrap" VerticalContentAlignment="Top" Padding="0,5" VerticalScrollBarVisibility="Auto" />
                                      Height="100" AcceptsReturn="True" TextWrapping="Wrap" VerticalContentAlignment="Top" Padding="0,5" VerticalScrollBarVisibility="Auto" />
                     <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="游戏参数" Margin="0,0,25,0" />
                     <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="游戏参数" Margin="0,0,25,0" />
-                    <local:MyTextBox x:Name="TextAdvanceGame" Grid.Row="2" Grid.ColumnSpan="2" Tag="VersionAdvanceGame" Grid.Column="1" ToolTip="文本框中的内容将会被直接拼合在启动参数的末尾。&#xa;例如,输入 --demo 则会以试玩模式启动游戏。&#xa;支持 Minecraft 版本 JSON 中的字符串替换标记,例如 ${library_directory}。&#xa;&#xa;若留空,则跟随全局设置的值。" HintText="跟随全局设置" />
+                    <local:MyTextBox x:Name="TextAdvanceGame" Grid.Row="2" Grid.ColumnSpan="2" Tag="VersionAdvanceGame" Grid.Column="1" ToolTip="文本框中的内容将会被直接拼合在启动参数的末尾。&#xa;例如,输入 --demo 则会以试玩模式启动游戏。&#xa;&#xa;支持 Minecraft 版本 JSON 中的字符串替换标记,例如 ${library_directory}。&#xa;也支持使用 PCL 的替换标记,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。&#xa;&#xa;若留空,则跟随全局设置的值。" HintText="跟随全局设置" />
                     <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="启动前执行命令" Margin="0,0,25,3" />
                     <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="启动前执行命令" Margin="0,0,25,3" />
                     <local:MyTextBox x:Name="TextAdvanceRun" Grid.Row="4" Grid.ColumnSpan="2" Tag="VersionAdvanceRun" Grid.Column="1" HintText="" Margin="0,0,0,3"
                     <local:MyTextBox x:Name="TextAdvanceRun" Grid.Row="4" Grid.ColumnSpan="2" Tag="VersionAdvanceRun" Grid.Column="1" HintText="" Margin="0,0,0,3"
-                                     ToolTip="该项不会覆盖全局设置:启动时会先执行全局设置的命令,再执行版本设置的命令。&#xa;&#xa;在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xa;&#xa;可以使用以下替换标记实现相对路径(路径均以 \ 结尾):&#xa; · {path}:PCL 的 exe 文件所在的文件夹&#xa; · {minecraft}:.minecraft 文件夹&#xa; · {verpath}:版本文件夹(.minecraft\versions\版本名\)&#xa; · {verindie}:开启版本隔离时等同版本文件夹,未开启时等同 .minecraft 文件夹&#xa; · {java}:游戏运行时的 Java 文件夹&#xa;&#xa;除此之外,也支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {uuid}:玩家的 UUID&#xa; · {name}:游戏版本名&#xa; · {date}、{time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号&#xa;&#xa;例如:&#xa; · &quot;{verpath}test.exe&quot; :运行版本文件夹下的 test.exe 程序&#xa; · &quot;{java}java.exe&quot; -jar &quot;{verpath}test.jar&quot; :用 Java 运行版本文件夹下的 test.jar&#xa; · notepad &quot;{verindie}options.txt&quot; :使用记事本打开该版本的设置文件&#xa;&#xa;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。&#xa;执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。" />
+                                     ToolTip="该项不会覆盖全局设置:启动时会先执行全局设置的命令,再执行版本设置的命令。&#xa;&#xa;在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xa;&#xa;你可以用替换标记来动态改变其内容或实现相对路径,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。例如:&#xa; · &quot;{verpath}test.exe&quot; :运行版本文件夹下的 test.exe 程序&#xa; · &quot;{java}java.exe&quot; -jar &quot;{verpath}test.jar&quot; :用 Java 运行版本文件夹下的 test.jar&#xa; · notepad &quot;{verindie}options.txt&quot; :使用记事本打开该版本的设置文件&#xa;&#xa;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。&#xa;执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。" />
                     <local:MyCheckBox Grid.Row="5" Grid.Column="1" x:Name="CheckAdvanceRunWait" Tag="VersionAdvanceRunWait" Visibility="Collapsed"
                     <local:MyCheckBox Grid.Row="5" Grid.Column="1" x:Name="CheckAdvanceRunWait" Tag="VersionAdvanceRunWait" Visibility="Collapsed"
                                       Text="等待命令执行完成后再继续启动" Margin="0,5,0,0" />
                                       Text="等待命令执行完成后再继续启动" Margin="0,5,0,0" />
                     <StackPanel Margin="0,12,0,4" Grid.Row="6" Grid.ColumnSpan="2" HorizontalAlignment="Left">
                     <StackPanel Margin="0,12,0,4" Grid.Row="6" Grid.ColumnSpan="2" HorizontalAlignment="Left">

+ 1 - 1
Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb

@@ -98,7 +98,7 @@
             JavaSearchLoader.Start(IsForceRestart:=True)
             JavaSearchLoader.Start(IsForceRestart:=True)
 
 
             Log("[Setup] 已初始化版本独立设置")
             Log("[Setup] 已初始化版本独立设置")
-            Hint("已初始化版本独立设置!", HintType.Finish, False)
+            Hint("已初始化版本独立设置!", HintType.Green, False)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "初始化版本独立设置失败", LogLevel.Msgbox)
             Log(ex, "初始化版本独立设置失败", LogLevel.Msgbox)
         End Try
         End Try

+ 2 - 2
Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj

@@ -206,8 +206,8 @@
       <SubType>Designer</SubType>
       <SubType>Designer</SubType>
     </ApplicationDefinition>
     </ApplicationDefinition>
     <Compile Include="Controls\Behaviors\ClipboardInterceptor.vb" />
     <Compile Include="Controls\Behaviors\ClipboardInterceptor.vb" />
-    <Compile Include="Controls\IMyRadio.vb" />
-    <Compile Include="Controls\Interfaces.vb" />
+    <Compile Include="Controls\Behaviors\IMyRadio.vb" />
+    <Compile Include="Controls\Behaviors\Interfaces.vb" />
     <Compile Include="Controls\MyComboBox.vb" />
     <Compile Include="Controls\MyComboBox.vb" />
     <Compile Include="Controls\MyComboBoxItem.vb" />
     <Compile Include="Controls\MyComboBoxItem.vb" />
     <Compile Include="Controls\MyCard.vb" />
     <Compile Include="Controls\MyCard.vb" />

+ 23 - 111
Plain Craft Launcher 2/Resources/Custom.xaml

@@ -53,114 +53,37 @@
     </StackPanel>
     </StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
-<local:MyCard Title="事件 1:打开网页、弹出窗口" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
-    <StackPanel Margin="25,40,23,15">
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
+<local:MyCard Title="自定义事件" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
+	<StackPanel Margin="25,40,23,15">
+		<TextBlock Margin="0,0,0,10"
                     Text="将按钮的 EventType 属性设为 打开网页,然后在 EventData 属性中写入网址,即可通过点击按钮打开网页。" />
                     Text="将按钮的 EventType 属性设为 打开网页,然后在 EventData 属性中写入网址,即可通过点击按钮打开网页。" />
-        <local:MyButton Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
+		<local:MyButton Height="35" HorizontalAlignment="Left" Padding="20,0,20,0"
                     Text="打开 Minecraft Wiki" EventType="打开网页" EventData="https://zh.minecraft.wiki/" />
                     Text="打开 Minecraft Wiki" EventType="打开网页" EventData="https://zh.minecraft.wiki/" />
-        <TextBlock TextWrapping="Wrap" Margin="0,10,0,10"
+		<TextBlock Margin="0,10,0,10"
                     Text="或者将 EventType 属性改为 弹出窗口,然后在 EventData 属性中写入弹窗的标题与内容……" />
                     Text="或者将 EventType 属性改为 弹出窗口,然后在 EventData 属性中写入弹窗的标题与内容……" />
-        <local:MyButton Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
+		<local:MyButton Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
                     Text="显示一个弹窗" EventType="弹出窗口" EventData="这是标题|标题与内容以竖线间隔。\n你也可以直接在其中换行。" />
                     Text="显示一个弹窗" EventType="弹出窗口" EventData="这是标题|标题与内容以竖线间隔。\n你也可以直接在其中换行。" />
-        <TextBlock TextWrapping="Wrap" Margin="0,10,0,0"
+		<TextBlock Margin="0,10,0,0"
                     Text="local:MyTextButton 同样可以设置 EventType 与 EventData 让它具有特定功能。" />
                     Text="local:MyTextButton 同样可以设置 EventType 与 EventData 让它具有特定功能。" />
-        <local:MyTextButton Margin="0,8,0,0" HorizontalAlignment="Center"
-                    Text="点击打开知乎" EventType="打开网页" EventData="https://www.zhihu.com/" />
-    </StackPanel>
-</local:MyCard>
-
-<local:MyCard Title="事件 2:启动游戏" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
-    <StackPanel Margin="25,40,23,15">
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
-                    Text="将 EventType 设置为 启动游戏,EventData 设置为具体的游戏版本,即可通过点击按钮启动游戏。如果当前游戏文件夹没有该版本,则无法启动。" />
-        <local:MyButton Margin="0,4,0,10" Width="250" Height="35"
-                    Text="启动 Minecraft 1.12.2" EventType="启动游戏" EventData="1.12.2" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
-                    Text="在 EventData 后面添加一条竖线(|),竖线后填写服务器 IP,即可在启动该版本的同时自动进入服务器。" />
-        <local:MyButton Margin="0,4,0,0" Width="250" Height="35"
-                    Text="启动 1.20.1 并进入 Hypixel" EventType="启动游戏" EventData="1.20.1|mc.hypixel.net" ToolTip="服务器 IP:mc.hypixel.net" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
-                    Text="在 EventData 中填写 \current,表示启动当前被选中的游戏版本,同样可以在后面填写服务器 IP。" />
-        <local:MyButton Margin="0,4,0,10" Width="250" Height="35"
-                    Text="启动当前选中版本并进入 Hypixel" EventType="启动游戏" EventData="\current|mc.hypixel.net" ToolTip="启动当前版本" />
-    </StackPanel>
+		<local:MyTextButton Margin="0,8,0,0" HorizontalAlignment="Center"
+                    Text="点击打开哔哩哔哩" EventType="打开网页" EventData="https://www.bilibili.com/" />
+		<TextBlock Margin="0,30,0,0"
+                    Text="你也可以让一个控件触发多个事件。自定义事件的详细用法请参考下方的 Wiki。" />
+		<local:MyListItem Margin="-5,8,-5,5"
+            Title="自定义事件 - PCL Wiki" Info="点击查看自定义事件的完整列表与说明"
+            Logo="pack://application:,,,/images/Blocks/CommandBlock.png" Type="Clickable"
+		    EventType="打开网页" EventData="https://github.com/Meloong-Git/PCL/wiki/自定义事件" />
+	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
-<local:MyCard Title="事件 3:执行命令" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
-    <StackPanel Margin="25,40,23,15">
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
-                    Text="将 EventType 设置为 执行命令(或 打开文件),EventData 设置为文件路径,即可点击打开特定文件或启动程序。" />
-        <local:MyButton Margin="0,4,0,8" Width="200" Height="35"
-                    Text="打开记事本" EventType="执行命令" EventData="notepad.exe" />
-        <TextBlock TextWrapping="Wrap" Margin="0,4,0,0"
-                    Text="如果能在 PCL 文件夹中找到该文件,则会优先打开 PCL 文件夹中的文件。当然,你可以使用例如 C:\ 开头的绝对路径。" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
-                    Text="此外,PCL 会将文件中的 “花括号 path” 替换为 PCL 可执行文件所在文件夹({path}),可以用于执行特定程序或加载图片。" />
-        <local:MyButton Margin="0,4,0,10" Width="200" Height="35"
-                    Text="打开 PCL 日志" EventType="执行命令" EventData="Log1.txt" ToolTip="PCL 的日志文件就在 PCL 文件夹内,且名为 Log1.txt" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
-                    Text="就像为游戏指定服务器 IP 一样,你也可以在 EventData 的竖线后为程序添加启动参数。" />
-        <local:MyButton Margin="0,4,0,0" Width="200" Height="35"
-                    Text="定位游戏启动脚本" EventType="执行命令" EventData="explorer.exe|/select,LatestLaunch.bat" ToolTip="PCL 会将上一次启动 MC 的脚本保存在 PCL 文件夹内,且名为 LatestLaunch.bat。&#xA;点击按钮会打开文件管理器,并且借助参数要求它选中这个文件。" />
-    </StackPanel>
-</local:MyCard>
-
-<local:MyCard Title="事件 4:其他事件类型" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
-    <StackPanel Margin="25,40,23,15">
-        <TextBlock Margin="0,4,0,6" FontWeight="Bold"
-                    Text="EventType 共有以下种类:" />
-        <TextBlock Margin="0,0,0,4"
-                    Text="· 打开网页、启动游戏、打开文件:已在上方的卡片中介绍" />
-        <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
-                    Text="查看 PCL 源代码" EventType="打开网页" EventData="https://github.com/Meloong-Git/PCL/blob/main/Plain%20Craft%20Launcher%202/FormMain.xaml.vb"/>
-        <TextBlock Margin="0,0,0,4"
-                    Text="· 今日人品、清理垃圾、内存优化、安装整合包:触发对应功能" />
-        <TextBlock Margin="0,0,0,4"
-                    Text="· 复制文本:将 EventData 中的内容复制到剪贴板" />
-        <TextBlock Margin="0,0,0,4"
-                    Text="· 刷新主页:点击重新加载主页。如果主页文件为联网更新,这会重新下载主页内容" />
-        <TextBlock Margin="0,0,0,4"
-                    Text="· 下载文件:在点击时要求选择文件保存位置,并且下载 EventData 的链接中的文件&#xa;       EventData 中也能可选地指定文件名或目标文件夹,格式为:链接|文件名|文件夹" />
-        <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
-                    Text="下载百度的 Logo" EventType="下载文件" EventData="https://www.baidu.com/img/flexible/logo/pc/result.png" />
-        <TextBlock Margin="0,4,0,4"
-                    Text="· 打开帮助:跳转到 EventData 对应的帮助文件(输入相对 Help 文件夹的路径)"/>
-        <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
-                    Text="打开自定义帮助页面" EventType="打开帮助" EventData="帮助/自定义帮助.json"/>
-        <TextBlock Margin="0,4,0,4"
-                    Text="· 切换页面:强制切换到 PCL 的某一页面(尝试切换到不太对头的页面会出现不可预知的 Bug)&#xa;       EventData 为 页面种类|次要页面种类(PageType|PageSubType,例如 1|1)&#xa;       点击上方的 查看 PCL 源代码 按钮,在 1000 行左右的位置即可查找到对应数值"/>
-        <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
-                    Text="切换到下载页面" EventType="切换页面" EventData="1|1"/>
-        <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
-                    Text="切换到启动器设置" EventType="切换页面" EventData="3|2"/>
-		<TextBlock Margin="0,4,0,4"
-                    Text="· 下载版本:切换到 EventData 对应的 Minecraft 下载页面"/>
-		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
-                    Text="下载 Minecraft 1.12.2" EventType="下载版本" EventData="1.12.2"/>
-		<TextBlock Margin="0,4,0,4"
-                    Text="· 修改设置:将 PCL 的特定设置修改为特定值"/>
-		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
-                    Text="将窗口改为半透明" EventType="修改设置" EventData="UiLauncherTransparent|400"/>
-    </StackPanel>
-</local:MyCard>
-
-<local:MyCard Title="在单个按钮中执行多个事件" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
-    <StackPanel Margin="25,40,23,15">
-        <TextBlock Margin="0,4,0,6"
-                    Text="通过按钮的 CustomEvents 属性来指定多个事件,它们将挨个运行" />
-        <local:MyButton Margin="0,5,0,6" Width="400" Height="35"
-                    Text="打开 Minecraft Wiki,显示一个弹窗,然后打开知乎">
-            <local:MyButton.CustomEvents>
-                <local:CustomEvent EventType="打开网页" EventData="https://zh.minecraft.wiki/" />
-                <local:CustomEvent EventType="弹出窗口" EventData="刚刚打开了 Wiki|该弹窗关闭后将打开知乎" />
-                <local:CustomEvent EventType="打开网页" EventData="https://www.zhihu.com/" />
-            </local:MyButton.CustomEvents>
-        </local:MyButton>
-    </StackPanel>
+<local:MyCard Title="替换标记" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
+	<StackPanel Margin="25,40,23,10">
+		<local:MyListItem Margin="-5,0,-5,8"
+		                  EventType="打开帮助" EventData="个性化/替换标记.json" />
+	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
-<local:MyCard Title="StackPanel 横向布局" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
+<local:MyCard Title="横向布局" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
                     Text="你需要使用 StackPanel 在一行里塞下多个按钮。你可以仅在这个教学卡片的基础上稍作调整,来实现自己的按钮布局。" />
                     Text="你需要使用 StackPanel 在一行里塞下多个按钮。你可以仅在这个教学卡片的基础上稍作调整,来实现自己的按钮布局。" />
@@ -198,7 +121,7 @@
                     Logo="pack://application:,,,/images/Blocks/Grass.png"
                     Logo="pack://application:,,,/images/Blocks/Grass.png"
                     EventType="打开帮助" EventData="帮助/自定义帮助.json" Type="Clickable" />
                     EventType="打开帮助" EventData="帮助/自定义帮助.json" Type="Clickable" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
-                    Text="EventType、EventData 与按钮几乎一致,唯一不同的是,列表项不支持联网加载帮助文件。" />
+                    Text="EventType、EventData 与按钮几乎一致。唯一不同的是,列表项不支持联网加载帮助文件。" />
     </StackPanel>
     </StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
@@ -261,17 +184,6 @@
     </StackPanel>
     </StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
-<local:MyCard Title="替换标记" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
-    <StackPanel Margin="25,40,23,15">
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
-                    Text="在花括号 {} 中写入特定内容,PCL 会在加载时对其进行替换。&#xa;例如,path 会被替换为 PCL 可执行文件所在文件夹({path}),用于在 EventData 中指定特定文件,或是加载图片。" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
-                    Text="你也可以使用替换标记来获取随机内容:cave 会被替换为一条随机的回声洞内容,hint 则是 “你知道吗” 的内容。" />
-        <TextBlock TextWrapping="Wrap" Margin="10,6,0,5" Foreground="{DynamicResource ColorBrush3}"
-                    Text="你知道吗:{hint}" />
-    </StackPanel>
-</local:MyCard>
-
 <local:MyCard Title="进阶:Grid 布局" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
 <local:MyCard Title="进阶:Grid 布局" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"

二进制
Plain Craft Launcher 2/Resources/Help.zip